光栅化渲染器(五)2d坐标系与3d坐标系

发表于2018-07-04
评论0 3k浏览
前面我们基本完成了光栅渲染器的2d部分,接下来开始光栅渲染器的3d部分吧。不过前面的2d光栅渲染器实际上还有优化的空间,建议大家自己完善一下(实际上,由于博主迫切地想玩3d,实际上漏了很多算法的实现和优化,比如三角形光栅渲染用边缘方程填充,2d直线裁剪,避免重复光栅化等等)

设计思路和部分代码参考韦前辈大神的mini3d。

一、更改数据结构
typedef struct { float x, y,z,w=1; } vector_t;
typedef vector_t point_t;
typedef struct { float r, g, b, a; } color_t;
typedef struct { float m[4][4]; } matrix_t;//矩阵
typedef struct { point_t pos; color_t color; } vertex_t;

二、加入3d数学库
// 计算插值:t 为 [0, 1] 之间的数值
float interp(float x1, float x2, float t) { return x1 + (x2 - x1) * t; }
// 矢量插值,t取值 [0, 1]
void vector_interp(vector_t &z,  vector_t x1,  vector_t x2, float t) {
    z.x = interp(x1.x, x2.x, t);
    z.y = interp(x1.y, x2.y, t);
    z.z = interp(x1.z, x2.z, t);
}
//计算2点距离
float vector_distance( vector_t v1, vector_t v2) {
    float sq = (v1.x-v2.x) *(v1.x - v2.x) + (v1.y -v2.y)* (v1.y - v2.y)+(v1.z-v2.z)*(v1.z - v2.z);
    return (float)sqrt(sq);
}
// | v |
float vector_length(const vector_t *v) {
    float sq = v->x * v->x + v->y * v->y + v->z * v->z;
    return (float)sqrt(sq);
}
// z = x + y
void vector_add(vector_t *z, const vector_t *x, const vector_t *y) {
    z->x = x->x + y->x;
    z->y = x->y + y->y;
    z->z = x->z + y->z;
    z->w = 1.0;
}
// z = x - y
void vector_sub(vector_t *z, const vector_t *x, const vector_t *y) {
    z->x = x->x - y->x;
    z->y = x->y - y->y;
    z->z = x->z - y->z;
    z->w = 1.0;
}
// 矢量点乘
float vector_dotproduct(const vector_t *x, const vector_t *y) {
    return x->x * y->x + x->y * y->y + x->z * y->z;
}
// 矢量叉乘
void vector_crossproduct(vector_t *z, const vector_t *x, const vector_t *y) {
    float m1, m2, m3;
    m1 = x->y * y->z - x->z * y->y;
    m2 = x->z * y->x - x->x * y->z;
    m3 = x->x * y->y - x->y * y->x;
    z->x = m1;
    z->y = m2;
    z->z = m3;
    z->w = 1.0f;
}
// 矢量插值,t取值 [0, 1]
void vector_interp(vector_t *z, const vector_t *x1, const vector_t *x2, float t) {
    z->x = interp(x1->x, x2->x, t);
    z->y = interp(x1->y, x2->y, t);
    z->z = interp(x1->z, x2->z, t);
    z->w = 1.0f;
}
// 矢量归一化
void vector_normalize(vector_t *v) {
    float length = vector_length(v);
    if (length != 0.0f) {
        float inv = 1.0f / length;
        v->x *= inv;
        v->y *= inv;
        v->z *= inv;
    }
}
// c = a + b
void matrix_add(matrix_t *c, const matrix_t *a, const matrix_t *b) {
    int i, j;
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++)
            c->m[i][j] = a->m[i][j] + b->m[i][j];
    }
}
// c = a - b
void matrix_sub(matrix_t *c, const matrix_t *a, const matrix_t *b) {
    int i, j;
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++)
            c->m[i][j] = a->m[i][j] - b->m[i][j];
    }
}
// c = a * b
void matrix_mul(matrix_t *c, const matrix_t *a, const matrix_t *b) {
    matrix_t z;
    int i, j;
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            z.m[j][i] = (a->m[j][0] * b->m[0][i]) +
                (a->m[j][1] * b->m[1][i]) +
                (a->m[j][2] * b->m[2][i]) +
                (a->m[j][3] * b->m[3][i]);
        }
    }
    c[0] = z;
}
// c = a * f
void matrix_scale(matrix_t *c, const matrix_t *a, float f) {
    int i, j;
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++)
            c->m[i][j] = a->m[i][j] * f;
    }
}
// y = x * m
void matrix_apply(vector_t *y, const vector_t *x, const matrix_t *m) {
    float X = x->x, Y = x->y, Z = x->z, W = x->w;
    y->x = X * m->m[0][0] + Y * m->m[1][0] + Z * m->m[2][0] + W * m->m[3][0];
    y->y = X * m->m[0][1] + Y * m->m[1][1] + Z * m->m[2][1] + W * m->m[3][1];
    y->z = X * m->m[0][2] + Y * m->m[1][2] + Z * m->m[2][2] + W * m->m[3][2];
    y->w = X * m->m[0][3] + Y * m->m[1][3] + Z * m->m[2][3] + W * m->m[3][3];
}
void matrix_set_identity(matrix_t *m) {
    m->m[0][0] = m->m[1][1] = m->m[2][2] = m->m[3][3] = 1.0f;
    m->m[0][1] = m->m[0][2] = m->m[0][3] = 0.0f;
    m->m[1][0] = m->m[1][2] = m->m[1][3] = 0.0f;
    m->m[2][0] = m->m[2][1] = m->m[2][3] = 0.0f;
    m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;
}
void matrix_set_zero(matrix_t *m) {
    m->m[0][0] = m->m[0][1] = m->m[0][2] = m->m[0][3] = 0.0f;
    m->m[1][0] = m->m[1][1] = m->m[1][2] = m->m[1][3] = 0.0f;
    m->m[2][0] = m->m[2][1] = m->m[2][2] = m->m[2][3] = 0.0f;
    m->m[3][0] = m->m[3][1] = m->m[3][2] = m->m[3][3] = 0.0f;
}
// 平移变换
void matrix_set_translate(matrix_t *m, float x, float y, float z) {
    matrix_set_identity(m);
    m->m[3][0] = x;
    m->m[3][1] = y;
    m->m[3][2] = z;
}
// 缩放变换
void matrix_set_scale(matrix_t *m, float x, float y, float z) {
    matrix_set_identity(m);
    m->m[0][0] = x;
    m->m[1][1] = y;
    m->m[2][2] = z;
}
// 旋转矩阵
void matrix_set_rotate(matrix_t *m, float x, float y, float z, float theta) {
    float qsin = (float)sin(theta * 0.5f);
    float qcos = (float)cos(theta * 0.5f);
    vector_t vec = { x, y, z, 1.0f };
    float w = qcos;
    vector_normalize(&vec);
    x = vec.x * qsin;
    y = vec.y * qsin;
    z = vec.z * qsin;
    m->m[0][0] = 1 - 2 * y * y - 2 * z * z;
    m->m[1][0] = 2 * x * y - 2 * w * z;
    m->m[2][0] = 2 * x * z + 2 * w * y;
    m->m[0][1] = 2 * x * y + 2 * w * z;
    m->m[1][1] = 1 - 2 * x * x - 2 * z * z;
    m->m[2][1] = 2 * y * z - 2 * w * x;
    m->m[0][2] = 2 * x * z - 2 * w * y;
    m->m[1][2] = 2 * y * z + 2 * w * x;
    m->m[2][2] = 1 - 2 * x * x - 2 * y * y;
    m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
    m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;
    m->m[3][3] = 1.0f;
}

以上代码完全照抄mid3d项目,实际上矩阵变换,矢量点乘等的代码形式。强烈建议大家看懂甚至动手推导一下,不理解的去查阅《3d数学基础 图形与游戏开发》

三、加入坐标系统
/=====================================================================
// 坐标变换
//=====================================================================
typedef struct {
    matrix_t world;         // 世界坐标变换
    matrix_t view;          // 摄影机坐标变换
    matrix_t projection;    // 投影变换
    matrix_t transform;     // transform = world * view * projection
}   transform_t;
transform_t Transform;//全局变量
// 矩阵更新,计算 transform = world * view * projection
void transform_update(transform_t *ts) {
    matrix_t m;
    matrix_mul(&m, &ts->world, &ts->view);
    matrix_mul(&ts->transform, &m, &ts->projection);
}
// 将矢量 x 进行 project 
void transform_apply( transform_t *ts, vector_t *y,  vector_t *x) {
    matrix_apply(y, x, &ts->transform);
}
// 世界坐标到相机坐标
void transform_homogenize( transform_t *ts, vector_t *y,  vector_t *x) {
    float rhw = 1.0f / x->w;
    y->x = (x->x * rhw + 1.0f) * SCREEN_WIDTH * 0.5f;
    y->y = (1.0f - x->y * rhw) * SCREEN_HEIGHT * 0.5f;
    y->z = x->z * rhw;
    y->w = 1.0f;
}
// D3DXMatrixPerspectiveFovLH
void matrix_set_perspective(matrix_t *m, float fovy, float aspect, float zn, float zf) {
    float fax = 1.0f / (float)tan(fovy * 0.5f);
    matrix_set_zero(m);
    m->m[0][0] = (float)(fax / aspect);
    m->m[1][1] = (float)(fax);
    m->m[2][2] = zf / (zf - zn);
    m->m[3][2] = -zn * zf / (zf - zn);
    m->m[2][3] = 1;
}

四、获取投影到屏幕上的2d点

更改三角形光栅化
void DrawTriangle(vertex_t ve1, vertex_t ve2, vertex_t ve3)
{
    color_t c{ 1.0,1.0,0,1.0 };//黄色
    point_t p1 = ve1.pos;
    point_t p2 = ve2.pos;
    point_t p3 = ve3.pos;
    point_t v1, v2, v3, c1, c2, c3;
    // 按照 Transform 变化
    transform_apply(&Transform, &c1, &p1);
    transform_apply(&Transform, &c2, &p2);
    transform_apply(&Transform, &c3, &p3);
    // 归一化
    transform_homogenize(&Transform, &v1, &c1);
    transform_homogenize(&Transform, &v2, &c2);
    transform_homogenize(&Transform, &v3, &c3);
    if (v1.x == v2.x&&v1.x == v3.x) return;
    if (v1.y == v2.y&&v1.y == v3.y) return;
    //DrawLine(v1, v2,c);
    //DrawLine(v2, v3,c);
    //DrawLine(v3, v1,c);
    vector<float> PointY{ v1.y,v2.y,v3.y };
    sort(PointY.begin(), PointY.end());
    float midY = PointY[1];
    float minY = PointY[0];
    float maxY = PointY[2];
    point_t MaxYPoint;
    point_t MidYPoint;
    point_t MinYPoint;
    if (midY != minY && midY != maxY)
    {
        if (midY == v1.y)
        {
            MidYPoint = v1;
            if (maxY == v2.y)
            {
                MaxYPoint = v2;
                MinYPoint = v3;
            }
            if (maxY == v3.y)
            {
                MaxYPoint = v3;
                MinYPoint = v2;
            }
        }
        else if (midY == v2.y)
        {
            MidYPoint = v2;
            if (maxY == v1.y)
            {
                MaxYPoint = v1;
                MinYPoint = v3;
            }
            if (maxY == v3.y)
            {
                MaxYPoint = v3;
                MinYPoint = v1;
            }
        }
        else if (midY == v3.y)
        {
            MidYPoint = v3;
            if (maxY == v1.y)
            {
                MaxYPoint = v1;
                MinYPoint = v2;
            }
            if (maxY == v2.y)
            {
                MaxYPoint = v2;
                MinYPoint = v1;
            }
        }
        point_t newV;
        float t = (midY - maxY) / (minY - maxY);
        newV.x = interp(MaxYPoint.x, MinYPoint.x, t);
        newV.y = midY;
        DrawTriangle_ScanConversion(MaxYPoint, MidYPoint, newV, c);
        DrawTriangle_ScanConversion(MinYPoint, MidYPoint, newV, c);
    }
    else
    {
        if (v1.y == v2.y) DrawTriangle_ScanConversion(v3, v1, v2, c);
        if (v1.y == v3.y) DrawTriangle_ScanConversion(v2, v1, v3, c);
        if (v3.y == v2.y) DrawTriangle_ScanConversion(v1, v3, v2, c);
    }
}
可以看到我们使用了transform_apply和transform_homogenize获取到了3d点到屏幕上的坐标。

五、顶点输入和立方体绘制
vertex_t mesh[8] = {
    { { 1, -1,  1, 1 }},
    { { -1, -1,  1, 1 } },
    { { -1,  1,  1, 1 }},
    { { 1,  1,  1, 1 } },
    { { 1, -1, -1, 1 } },
    { { -1, -1, -1, 1  },
    { { -1,  1, -1, 1 }},
    { { 1,  1, -1, 1 } },
};

画正方形
void draw_plane( int a, int b, int c, int d) {
    vertex_t p1 = mesh[a], p2 = mesh[b], p3 = mesh[c], p4 = mesh[d];
    DrawTriangle(p1, p2, p3);
    DrawTriangle(p3, p4, p1);
}

画立方体
//************************画立方体*******************
void DrawBox()
{
    draw_plane( 0, 1, 2, 3);
    draw_plane(4, 5, 6, 7);
    draw_plane( 0, 4, 5, 1);
    draw_plane( 1, 5, 6, 2);
    draw_plane( 2, 6, 7, 3);
    draw_plane( 3, 7, 4, 0);
}

六、摄像头控制
//************************摄像头控制*******************
// 设置摄像机
void matrix_set_lookat(matrix_t *m, const vector_t *eye, const vector_t *at, const vector_t *up) {
    vector_t xaxis, yaxis, zaxis;
    vector_sub(&zaxis, at, eye);
    vector_normalize(&zaxis);
    vector_crossproduct(&xaxis, up, &zaxis);
    vector_normalize(&xaxis);
    vector_crossproduct(&yaxis, &zaxis, &xaxis);
    m->m[0][0] = xaxis.x;
    m->m[1][0] = xaxis.y;
    m->m[2][0] = xaxis.z;
    m->m[3][0] = -vector_dotproduct(&xaxis, eye);
    m->m[0][1] = yaxis.x;
    m->m[1][1] = yaxis.y;
    m->m[2][1] = yaxis.z;
    m->m[3][1] = -vector_dotproduct(&yaxis, eye);
    m->m[0][2] = zaxis.x;
    m->m[1][2] = zaxis.y;
    m->m[2][2] = zaxis.z;
    m->m[3][2] = -vector_dotproduct(&zaxis, eye);
    m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
    m->m[3][3] = 1.0f;
}
void camera_at_zero(transform_t *transform, float x, float y, float z) {
    point_t eye = { x, y, z, 1 }, at = { 0, 0, 0, 1 }, up = { 0, 0, 1, 1 };
    matrix_set_lookat(&transform->view, &eye, &at, &up);
    transform_update(transform);
}

七、修改重绘函数
float CameraPos=3.5;
float Alpha = 1;

以上2个是相关的全局变量
// 重绘函数
void myDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT);     // 清屏幕 
    transform_init(&Transform);//初始化摄像头
    camera_at_zero(&Transform, CameraPos, 0, 0);
    glBegin(GL_POINTS);
    DrawBox();
    glEnd();
    glFlush();                         // 将所有输出到显示屏上 
}

修改后运行程序可以看到一个正方形,这样不好看,根本不能判断是不是3d,所以我们旋转它

八、旋转立方体
void DrawBox()
{
    matrix_t m;
    matrix_set_rotate(&m, -1, 1, 1, Alpha);
    Transform.world = m;
    transform_update(&Transform);
    draw_plane( 0, 1, 2, 3);
    draw_plane(4, 5, 6, 7);
    draw_plane( 0, 4, 5, 1);
    draw_plane( 1, 5, 6, 2);
    draw_plane( 2, 6, 7, 3);
    draw_plane( 3, 7, 4, 0);
}

这样静止不动没意思,我们来控制摄像头来改变我们所看到的立方体吧。

九、键盘控制摄像头
//************************键盘鼠标输入*******************
void myKeyBoard(unsigned char theKey, int MouseX, int MouseY)
{
    switch (theKey)
    {
    case 'w':
        CameraPos -= 0.1;
        myDisplay();//重绘函数不能实时更新,所以我们需要这样,如果有更好的方法,希望大家告知
        break;
    case 's':
        CameraPos += 0.1;
        myDisplay();
        break;
    case 'a':
        Alpha += 0.1;
        myDisplay();
        break;
    case 'd':
        Alpha -= 0.1;
        myDisplay();
        break;
    default:
        break;
    }
}

接下来注册键盘输入事件
void main(int argc, char **argv)
{
    glutInit(&argc, argv);          // 初始化工具包
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // 设置显式模式
    glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT);     // 设置窗口大小
    glutInitWindowPosition(200, 0); // 设置窗口位置
    glutCreateWindow(SCREEN_TITLE); // 打开屏幕窗口
    glutDisplayFunc(myDisplay);     // 注册绘制函数
    myInit();
    glutKeyboardFunc( myKeyBoard);//注册键盘输入函数
    glutMainLoop();              // 进入主循环
}

好了,完成了,大家可以尝试通过a,s,d,w按键控制摄像头,我只是贴了我的代码,矩阵变换3d变换旋转还是建议大家自己推导一下
来自:https://blog.csdn.net/qq_34244317/article/details/78398837

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引