图形学基础 | 三角形光栅化

从零实现3D图像引擎:(15)三角形的光栅化

1 为什么需要光栅化三角形

  • 不能总让我们的引擎显示线框,要支持实心颜色、光照还有纹理贴图,这些都需要光栅化一个三角形作为支持

2 三角形的类型

在这里插入图片描述

3 平底三角形光栅化

在这里插入图片描述
光栅化平底三角形的原理很简单,就是从上往下画横线。
在图里我们取任意的一条光栅化直线,
这条直线左边的端点x值为XL,右边的为XR。
y值就不用考虑了,因为这些线是从上往下画的,所以y就是从y0一直++,直到y1或者y2。

4. 光栅化平顶三角形

  • 与平底三角形类似

5 光栅化任意三角形

在这里插入图片描述

方法: 将三角形划分为两个三角形. 1) 平底 2) 平顶

每个三角形需要记录的信息:

  1. top
  2. bottom
  3. left直线
  4. right直线

6 具体代码

6.1 拆分三角形

// 将三角形拆成 平三角形
// https://blog.csdn.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 :

  1. 计算出j行上 left 和 right 线上的 两个顶点. 对这个两个顶点进行插值.
  2. 根据左右两边的端点,初始化计算出扫描线的起点和步长
  3. 绘制扫描线.

仅是个人理解的光栅化三角形的一种做法. 具体做法会有很大不同.

// 主渲染函数
	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;
		}
	}

猜你喜欢

转载自blog.csdn.net/qjh5606/article/details/88915566
今日推荐