图形学基础 | 三角形光栅化
1 为什么需要光栅化三角形
- 不能总让我们的引擎显示线框,要支持实心颜色、光照还有纹理贴图,这些都需要光栅化一个三角形作为支持
2 三角形的类型
3 平底三角形光栅化
光栅化平底三角形的原理很简单,就是从上往下画横线。
在图里我们取任意的一条光栅化直线,
这条直线左边的端点x值为XL,右边的为XR。
y值就不用考虑了,因为这些线是从上往下画的,所以y就是从y0一直++,直到y1或者y2。
4. 光栅化平顶三角形
- 与平底三角形类似
5 光栅化任意三角形
方法: 将三角形划分为两个三角形. 1) 平底 2) 平顶
每个三角形需要记录的信息:
- top
- bottom
- left直线
- right直线
6 具体代码
6.1 拆分三角形
// 将三角形拆成 平三角形
// https://blog.****.net/cppyin/article/details/6232453
int trapezoid_init_triangle(trapezoid_t *trap, const vertex_t *p1,
const vertex_t *p2, const vertex_t *p3) {
const vertex_t *p;
float k, x;
// 排序一下 p1.y<p2.y<p3.y
if (p1->pos.y > p2->pos.y) p = p1, p1 = p2, p2 = p;
if (p1->pos.y > p3->pos.y) p = p1, p1 = p3, p3 = p;
if (p2->pos.y > p3->pos.y) p = p2, p2 = p3, p3 = p;
// 构不成三角形,返回0
if (p1->pos.y == p2->pos.y && p1->pos.y == p3->pos.y) return 0;
if (p1->pos.x == p2->pos.x && p1->pos.x == p3->pos.x) return 0;
// 平顶,向下的三角形
if (p1->pos.y == p2->pos.y) {
// 保证 p1.pos.x<p2.pos.x
if (p1->pos.x > p2->pos.x) p = p1, p1 = p2, p2 = p;
trap[0].top = p1->pos.y; // 上面
trap[0].bottom = p3->pos.y; // 底部
// 三角形的左边 p1->p3
trap[0].left.v1 = *p1;
trap[0].left.v2 = *p3;
// 三角形的右边 p2->p3
trap[0].right.v1 = *p2;
trap[0].right.v2 = *p3;
return (trap[0].top < trap[0].bottom) ? 1 : 0;
}
// 平底三角,向上的三角形
if (p2->pos.y == p3->pos.y) {
// 保证 p2.pos.x<p3.pos.x
if (p2->pos.x > p3->pos.x) p = p2, p2 = p3, p3 = p;
trap[0].top = p1->pos.y;
trap[0].bottom = p2->pos.y;
// 三角形的左边 p1->p2
trap[0].left.v1 = *p1;
trap[0].left.v2 = *p2;
// 三角形的右边 p1->p3
trap[0].right.v1 = *p1;
trap[0].right.v2 = *p3;
return (trap[0].top < trap[0].bottom) ? 1 : 0;
}
// 需要对三角形进行划分,0:平底,1:平顶
trap[0].top = p1->pos.y;
trap[0].bottom = p2->pos.y;
trap[1].top = p2->pos.y;
trap[1].bottom = p3->pos.y;
k = (p3->pos.y - p1->pos.y) / (p2->pos.y - p1->pos.y);
x = p1->pos.x + (p2->pos.x - p1->pos.x) * k;
// 三角形分为右边为主和左边为主
if (x <= p3->pos.x) { // 右边为主
trap[0].left.v1 = *p1;
trap[0].left.v2 = *p2;
trap[0].right.v1 = *p1;
trap[0].right.v2 = *p3;
trap[1].left.v1 = *p2;
trap[1].left.v2 = *p3;
trap[1].right.v1 = *p1;
trap[1].right.v2 = *p3;
}
else { // 左边为主
trap[0].left.v1 = *p1;
trap[0].left.v2 = *p3;
trap[0].right.v1 = *p1;
trap[0].right.v2 = *p2;
trap[1].left.v1 = *p1;
trap[1].left.v2 = *p3;
trap[1].right.v1 = *p2;
trap[1].right.v2 = *p3;
}
return 2;
}
6.2 插值三角形
通过扫描线进行插值
从 top 遍历到 bottom j :
- 计算出j行上 left 和 right 线上的 两个顶点. 对这个两个顶点进行插值.
- 根据左右两边的端点,初始化计算出扫描线的起点和步长
- 绘制扫描线.
仅是个人理解的光栅化三角形的一种做法. 具体做法会有很大不同.
// 主渲染函数
void device_render_trap(trapezoid_t *trap) {
// 扫描线插值
scanline_t scanline;
int j, top, bottom;
top = (int)(trap->top + 0.5f);
bottom = (int)(trap->bottom + 0.5f);
for (j = top; j < bottom; ++j) {
// 合法
if (j >= 0 && j < height) {
// 1 根据 j坐标计算出left和right线上的两个顶点
trapezoid_edge_interp(trap, (float)j + 0.5f);
// 2. 根据左右两边的端点,初始化计算出扫描线的起点和步长
trapezoid_init_scan_line(trap, &scanline, j);
// 3. 绘制扫描线
device_draw_scanline(&scanline);
}
if (j >= height) break;
}
}