光栅化渲染器(六)着色准备和深度缓存实现
发表于2018-07-05
前面介绍完了坐标系,下面就来介绍下如何去更改一下顶点着色,让这个渲染器更漂亮。
首先是数据结构
更改数据结构
typedef struct { point_t pos; color_t color; } vertex_t;
更改顶点输入
vertex_t mesh[8] = { { { 1, -1, 1, 1 },{ 1.0f, 0.2f, 0.2f } }, { { -1, -1, 1, 1 },{ 0.2f, 1.0f, 0.2f } }, { { -1, 1, 1, 1 },{ 0.2f, 0.2f, 1.0f }}, { { 1, 1, 1, 1 },{ 1.0f, 0.2f, 1.0f } }, { { 1, -1, -1, 1 },{ 1.0f, 1.0f, 0.2f } }, { { -1, -1, -1, 1 },{ 0.2f, 1.0f, 1.0f } }, { { -1, 1, -1, 1 },{ 1.0f, 0.3f, 0.3f }}, { { 1, 1, -1, 1 },{ 0.2f, 1.0f, 0.3f } }, };
更改画三角形算法
void DrawTriangle(vertex_t ve1, vertex_t ve2, vertex_t ve3) { 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]; vertex_t MaxYPoint; vertex_t MidYPoint; vertex_t MinYPoint; if (midY != minY && midY != maxY) { if (midY == v1.y) { MidYPoint.pos = v1; MidYPoint.color = ve1.color; if (maxY == v2.y) { MaxYPoint.pos= v2; MaxYPoint.color = ve2.color; MinYPoint.pos= v3; MinYPoint.color = ve3.color; } if (maxY == v3.y) { MaxYPoint.pos= v3; MaxYPoint.color = ve3.color; MinYPoint.pos= v2; MinYPoint.color = ve2.color; } } else if (midY == v2.y) { MidYPoint.pos = v2; MidYPoint.color = ve2.color; if (maxY == v1.y) { MaxYPoint.pos = v1; MaxYPoint.color = ve1.color; MinYPoint.pos = v3; MinYPoint.color = ve3.color; } if (maxY == v3.y) { MaxYPoint.pos = v3; MaxYPoint.color = ve3.color; MinYPoint.pos = v1; MinYPoint.color = ve1.color; } } else if (midY == v3.y) { MidYPoint.pos = v3; MidYPoint.color = ve3.color; if (maxY == v1.y) { MaxYPoint.pos = v1; MaxYPoint.color = ve1.color; MinYPoint.pos = v2; MinYPoint.color = ve2.color; } if (maxY == v2.y) { MaxYPoint.pos = v2; MaxYPoint.color = ve2.color; MinYPoint.pos = v1; MinYPoint.color = ve1.color; } } vertex_t newV; float t = (midY - maxY) / (minY - maxY); newV.pos.x = interp(MaxYPoint.pos.x, MinYPoint.pos.x, t); newV.pos.y = midY; color_interp(newV.color,MaxYPoint.color, MinYPoint.color, t); DrawTriangle_ScanConversion(MaxYPoint, MidYPoint, newV); DrawTriangle_ScanConversion(MinYPoint, MidYPoint, newV); } else { if (v1.y == v2.y) DrawTriangle_ScanConversion(ve3, ve1, ve2); if (v1.y == v3.y) DrawTriangle_ScanConversion(ve2, ve1, ve3); if (v3.y == v2.y) DrawTriangle_ScanConversion(ve1, ve3, ve2); } }
接下来更改画线算法,由于代码太多,就不一一贴出
加入画点函数
//画点 void DrawPoint(int x,int y,color_t c) { glColor4f(c.r, c.g, c.b, c.a); //glColor4f(1, 1, 0,0.5); glVertex2i((int)x, (int)y); }
发现了几个问题:
1.立方体的某些本应被遮挡住的面被绘制在了这个立方体其他面之上,学过opengl,Direct的应该知道些,我们还缺少z缓冲也就是深度信息,
2.如果一直按f会出现视角中物体无法改变的现象
增加视锥裁剪,裁剪不在摄像头视锥内的物体
// 检查齐次坐标同 cvv 的边界用于视锥裁剪 int transform_check_cvv(const vector_t *v) { float w = v->w; int check = 0; if (v->z < 0.0f) check |= 1; if (v->z > w) check |= 2; if (v->x < -w) check |= 4; if (v->x > w) check |= 8; if (v->y < -w) check |= 16; if (v->y > w) check |= 32; return check; }
void DrawTriangle(vertex_t ve1, vertex_t ve2, vertex_t ve3) { 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); // 裁剪,注意此处可以完善为具体判断几个点在 cvv内以及同cvv相交平面的坐标比例 // 进行进一步精细裁剪,将一个分解为几个完全处在 cvv内的三角形 if (transform_check_cvv(&c1) != 0) return; if (transform_check_cvv(&c2) != 0) return; if (transform_check_cvv(&c3) != 0) return; // 归一化 transform_homogenize(&Transform, &v1, &c1); transform_homogenize(&Transform, &v2, &c2); transform_homogenize(&Transform, &v3, &c3);
裁剪成功
增加深度缓存
博主选择了一种可能非常高耗的方法,就是用vector存储深度信息和颜色信息
//缓存 typedef struct { color_t c = {0 ,0,0,1 }; float depth=INT_MAX; }Buffer_t; vector<vector<Buffer_t>> _buffer; void BufferInit() { Buffer_t _b; vector<Buffer_t> t(SCREEN_WIDTH * 2,_b); _buffer = vector<vector<Buffer_t>>(SCREEN_HEIGHT * 2, t); } void BufferUpdate() { for (int i = 0; i <= SCREEN_HEIGHT; i++) { for (int j = 0; j <= SCREEN_WIDTH; j++) { _buffer[i][j].depth = INT_MAX; } } }
缓存初始化
//<<<<<<<<<<<<<<<<<<<<<<<< main >>>>>>>>>>>>>>>>>>>>>> void main(int argc, char **argv) { BufferInit();
接下来在重绘函数中加入缓存更新函数
// 重绘函数 void myDisplay(void) { BufferUpdate();
接下来更改画三角形的算法,需要z,w值传给画线算法以及最终的画点函数
void DrawTrigngleCH(vertex_t &M,point_t v1,point_t c1,vertex_t ve1) { M.pos = v1; M.pos.z = v1.z; M.pos.w = c1.w; M.color = ve1.color; } void DrawTriangle(vertex_t ve1, vertex_t ve2, vertex_t ve3) { 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); // 裁剪,注意此处可以完善为具体判断几个点在 cvv内以及同cvv相交平面的坐标比例 // 进行进一步精细裁剪,将一个分解为几个完全处在 cvv内的三角形 if (transform_check_cvv(&c1) != 0) return; if (transform_check_cvv(&c2) != 0) return; if (transform_check_cvv(&c3) != 0) return; // 归一化 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]; vertex_t MaxYPoint; vertex_t MidYPoint; vertex_t MinYPoint; if (midY != minY && midY != maxY) { if (midY == v1.y) { /* MidYPoint.pos = v1; MidYPoint.pos.z = c1.z; MidYPoint.color = ve1.color;*/ DrawTrigngleCH(MidYPoint,v1,c1, ve1); if (maxY == v2.y) { DrawTrigngleCH(MaxYPoint, v2, c2, ve2); DrawTrigngleCH(MinYPoint, v3, c3, ve3); } if (maxY == v3.y) { DrawTrigngleCH(MaxYPoint, v3, c3, ve3); DrawTrigngleCH(MinYPoint, v2, c2, ve2); } } else if (midY == v2.y) { DrawTrigngleCH(MidYPoint, v2, c2, ve2); if (maxY == v1.y) { DrawTrigngleCH(MaxYPoint, v1, c1, ve1); DrawTrigngleCH(MinYPoint, v3, c3, ve3); } if (maxY == v3.y) { DrawTrigngleCH(MaxYPoint, v3, c3, ve3); DrawTrigngleCH(MinYPoint, v1, c1, ve1); } } else if (midY == v3.y) { DrawTrigngleCH(MidYPoint, v3, c3, ve3); if (maxY == v1.y) { DrawTrigngleCH(MaxYPoint, v1, c1, ve1); DrawTrigngleCH(MinYPoint, v2, c2, ve2); } if (maxY == v2.y) { DrawTrigngleCH(MaxYPoint, v2, c2, ve2); DrawTrigngleCH(MinYPoint, v1, c1, ve1); } } vertex_t newV; float t = (midY - maxY) / (minY - maxY); newV.pos.x = interp(MaxYPoint.pos.x, MinYPoint.pos.x, t); newV.pos.z= interp(MaxYPoint.pos.z, MinYPoint.pos.z, t); newV.pos.y = midY; newV.pos.w = interp(MaxYPoint.pos.w, MinYPoint.pos.w, t); color_interp(newV.color, MaxYPoint.color, MinYPoint.color, t); DrawTriangle_ScanConversion(MaxYPoint, MidYPoint, newV); DrawTriangle_ScanConversion(MinYPoint, MidYPoint, newV); } else { ve1.pos.z = v1.z; ve2.pos.z = v2.z; ve3.pos.z = v3.z; ve1.pos.w= c1.w; ve2.pos.w= c2.w; ve3.pos.w= c3.w; if (v1.y == v2.y) DrawTriangle_ScanConversion(ve3, ve1, ve2); if (v1.y == v3.y) DrawTriangle_ScanConversion(ve2, ve1, ve3); if (v3.y == v2.y) DrawTriangle_ScanConversion(ve1, ve3, ve2); } }
画线里也要更新z,w值并传给画点函数由于代码太多就不一一贴出,接下来是画点函数
还记得视锥裁剪算法吗
// 检查齐次坐标同 cvv 的边界用于视锥裁剪 int transform_check_cvv(const vector_t *v) { float w = v->w; int check = 0; if (v->z < 0.0f) check |= 1; if (v->z > w) check |= 2; if (v->x < -w) check |= 4; if (v->x > w) check |= 8; if (v->y < -w) check |= 16; if (v->y > w) check |= 32; return check; }
所谓的深度就是w-z
//画点 void DrawPoint(int x,int y,color_t c,float z,float w) { float d = w - z*w; //glColor4f(1, 1, 0,0.5); if (_buffer[x][y].depth <d) { c = _buffer[x][y].c; return; } else { _buffer[x][y].depth =d; _buffer[x][y].c = c; } glColor4f(c.r, c.g, c.b, c.a); glVertex2i(x, y); }
最后上图