前面我们基本完成了光栅渲染器的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变换旋转还是建议大家自己推导一下

光栅渲染器(五)2d-3d坐标系相关推荐

  1. 从零开始实现3D软光栅渲染器 (1) 简介

    如何在2D屏幕上表示3D物体?这是学习3D编程必须要搞明白的事情.大家都知道,调用OpenGL的函数,给定三角形的3个顶点位置,颜色,就能在屏幕上画一个三角形,再加载一幅图片,就可以给这个三角形附上纹 ...

  2. [从零构建光栅渲染器] 6. 顶点和片元着色器的工作原理

    [从零构建光栅渲染器] 6. 顶点和片元着色器的工作原理 非常感谢和推荐Sokolov的教程,Sokolov使用500行C++代码实现一个光栅渲染器.教程学习过程非常平滑,从画点.线和三角形开始教学, ...

  3. [从零构建光栅渲染器] 3.隐藏面剃除 z-buffer(深度缓冲)

    非常感谢和推荐Sokolov的教程,Sokolov使用500行C++代码实现一个光栅渲染器.教程学习过程非常平滑,从画点.线和三角形开始教学,在逐步深入三维变换,投影,再到顶点着色器,片段着色器等等. ...

  4. Nvidia发布基于插值光栅化的微分渲染器(DIB-R)生成3D对象模型

    Nvidia研究人员将于本月在温哥华举行的年度神经信息处理系统会议(NeurIPS)上介绍他们的模型. 他们提出了一个完整的基于光栅化的微分渲染器,可以通过分析来计算梯度.当包裹在神经网络周围时,他们 ...

  5. 光栅渲染器(六)着色准备和深度缓存实现

    接下来更改一下顶点着色,让这个渲染器更漂亮 首先是数据结构 更改数据结构 typedef struct { point_t pos; color_t color; } vertex_t; 更改顶点输入 ...

  6. 用C# Bitmap作为画布写个3D软渲染器

    文章目录 Recoards 记录 图元光栅 Bitmap.SetPixel优化成LockBits/UnlockBits指针操作 Blend Projection 投影 Wireframe 线框 Sci ...

  7. html3d模型渲染,【SVG】纯clip-path打造的3D模型渲染器

    几天之前, 一个species-in-pieces的网站把我震到了(如下图), 出于一个优秀前端的敏锐嗅觉和原始本能, 我立刻祭出了看家法宝--Chrome开发者工具开始偷窥这个网站. 简单推敲之后, ...

  8. 计算机图形学-光栅渲染概述

    开篇 本篇主要讲的是计算机图形学中比较重要的主题之一,渲染,并且着重于讲述光栅化的渲染方式. 当然,我们要了解光栅渲染这个细分领域(当然这个领域也是及其庞大的),就应该知道它在整个的知识框架中是出于一 ...

  9. CVPR2021(Oral) 商汤、港中文实现单目人脸重建新突破: 基于生成网络的渲染器!几何形状更精准!渲染效果更真实!...

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 近日,商汤-港中文联合实验室提出基于风格化对抗生成器的人脸渲染器,用于取代传统图形学基于栅格化的渲染器 ...

最新文章

  1. MySQL数据库引擎快速指导
  2. SAP Spartacus url里默认electronics-spa的由来
  3. CentOS 6 rpm方式安装mysql
  4. 673. 最长递增子序列的个数
  5. 一个列表包揽所有你需要的Windows应用
  6. bzoj 4417: [Shoi2013]超级跳马(矩阵合并+快速幂)
  7. 部分网站为什么上不去_天机SEO:分析网站收录与排名的重要因素
  8. Smobiler客户端会话
  9. 解决svn文件图标不显示
  10. 大一计算机学实验报告,大学大学计算机实验报告
  11. 32位/64位CPU的32位/64位指的是什么?
  12. 安卓app开发方案_简谈企业最常用的三种安卓app开发语言
  13. centos7 查看multipath服务状态是start condition failed,/etc/multipath.conf was not met
  14. c语言五子棋对局结果存储,基于C语言五子棋小游戏总结.doc
  15. 抖音国庆小游戏是如何实现的?带你走近 Cocos
  16. 做马来西亚Lazada的选品技巧是什么
  17. java lisp,摸索JVM上的LISP[Java编程]
  18. java 访问网址并将返回结果的json数组转换为List<String>
  19. Markdown入门总结
  20. 2017跨境电商把脉:未来会员经济将成新引擎?

热门文章

  1. 完工后的决算书范本_装修竣工结算书范本谁能给份
  2. kubelet nodelost
  3. 350套前端网站模板
  4. MySQL备份报错mysqldump: Got error: 1045: Access denied for user ‘root‘@‘localhost‘ (using password: YES)
  5. thinkPHP中{$Think }用法
  6. WLC license管理
  7. 【论文笔记】Expanding holographic embeddings for knowledge completion
  8. BGP Aggregation – Suppress Map
  9. Mac下解决硬盘无法读取问题Mounty for NTFS - 免费让 Mac 原生支持移动硬盘/U盘 NTFS 读写的必备驱动应用
  10. IC 拔取器 rework station