Triangle Rasterization - 三角形光栅化器

项目目标

今天的目标是从零实现一个三角形光栅化器,这是现代3D图形渲染管线的核心组件之一。

光栅化器的职责是:将三角形(几何)转换为屏幕上的像素(图像)

我们将实现:

  • ✅ Barycentric 坐标算法 - 判断点是否在三角形内
  • ✅ Z-Buffer 深度测试 - 正确处理遮挡关系
  • ✅ 颜色插值 - 顶点颜色的平滑渐变
  • ✅ 边界框优化 - 只遍历必要的像素

实现过程

迭代历史

  1. 05:33 - 初始版本: 编写完整的光栅化器代码

    • 实现 Vec2/Vec3/Color 基础数据结构
    • 实现 barycentric() 重心坐标计算
    • 实现 rasterizeTriangle() 光栅化函数
    • 初始化帧缓冲和深度缓冲
  2. 05:34 - 编译错误: 缺少 <limits> 头文件

    • 错误信息:'numeric_limits' is not a member of 'std'
    • 原因:使用了 std::numeric_limits<float>::max() 但没有包含 <limits>
    • 修复:添加 #include <limits>
  3. 05:35 - ✅ 编译成功,运行成功

    • 生成 rasterization_output.png
    • 三个三角形正确渲染
  4. 05:37 - ✅ 量化验证通过

    • 红色三角形区域:RGB(220, 53, 54) ✅
    • 绿色三角形区域:RGB(46, 147, 119) ✅
    • 蓝色三角形(中心):RGB(62, 62, 240) ✅
    • 深度测试正确:蓝色三角形(z=0.3)遮挡了红色(z=0.5)和绿色(z=0.6)

核心代码

1. 重心坐标算法

重心坐标是判断点是否在三角形内的经典方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vec3 barycentric(const Vec2& p, const Vec2& a, const Vec2& b, const Vec2& c) {
Vec2 v0(c.x - a.x, c.y - a.y);
Vec2 v1(b.x - a.x, b.y - a.y);
Vec2 v2(p.x - a.x, p.y - a.y);

float den = v0.x * v1.y - v1.x * v0.y;
if (std::abs(den) < 1e-6) return Vec3(-1, 1, 1); // 退化三角形

float v = (v2.x * v1.y - v1.x * v2.y) / den;
float w = (v0.x * v2.y - v2.x * v0.y) / den;
float u = 1.0f - v - w;

return Vec3(u, v, w);
}

关键特性

  • 如果 u, v, w 都 ≥ 0,则点在三角形内
  • u + v + w = 1(标准化)
  • 可用于插值任何顶点属性(颜色、纹理坐标、法线等)

2. 光栅化主循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void rasterizeTriangle(
const Vertex& v0, const Vertex& v1, const Vertex& v2,
std::vector<Color>& framebuffer,
std::vector<float>& zbuffer,
int width, int height
) {
// 计算边界框(Bounding Box)
float minX = std::min({v0.pos.x, v1.pos.x, v2.pos.x});
float maxX = std::max({v0.pos.x, v1.pos.x, v2.pos.x});
float minY = std::min({v0.pos.y, v1.pos.y, v2.pos.y});
float maxY = std::max({v0.pos.y, v1.pos.y, v2.pos.y});

// 裁剪到屏幕范围
int x0 = std::max(0, (int)minX);
int x1 = std::min(width - 1, (int)maxX);
int y0 = std::max(0, (int)minY);
int y1 = std::min(height - 1, (int)maxY);

// 遍历边界框内的所有像素
for (int y = y0; y <= y1; y++) {
for (int x = x0; x <= x1; x++) {
Vec2 p(x + 0.5f, y + 0.5f); // 像素中心
Vec3 bc = barycentric(p, v0.pos, v1.pos, v2.pos);

// 检查点是否在三角形内
if (bc.x < 0 || bc.y < 0 || bc.z < 0) continue;

// 插值深度
float z = bc.x * v0.z + bc.y * v1.z + bc.z * v2.z;

// 深度测试
int idx = y * width + x;
if (z < zbuffer[idx]) {
zbuffer[idx] = z;

// 插值颜色
float r = bc.x * v0.color.r + bc.y * v1.color.r + bc.z * v2.color.r;
float g = bc.x * v0.color.g + bc.y * v1.color.g + bc.z * v2.color.g;
float b = bc.x * v0.color.b + bc.y * v1.color.b + bc.z * v2.color.b;

framebuffer[idx] = Color(
(unsigned char)r,
(unsigned char)g,
(unsigned char)b
);
}
}
}
}

优化要点

  1. 边界框 - 只遍历三角形覆盖的区域,而非整个屏幕
  2. 深度测试 - 只更新更近的像素(z < zbuffer[idx])
  3. 像素中心 - 采样点在像素中心(x+0.5, y+0.5)

运行结果

三角形光栅化输出

场景包含3个重叠的三角形:

  • 红色三角形(左侧)- 深度 z=0.5,红色渐变
  • 绿色三角形(右侧)- 深度 z=0.6,绿色渐变
  • 蓝色三角形(中心)- 深度 z=0.3(最前面),蓝色渐变

深度测试验证:蓝色三角形正确遮挡了红色和绿色三角形的重叠部分。

技术总结

学到的技术点

  1. Barycentric 坐标系统

    • 三角形内任意点可表示为顶点的加权组合
    • 权重和为1,且都非负时点在三角形内
    • 是插值的数学基础
  2. Z-Buffer 算法

    • 解决可见性问题的经典算法(1974年 Catmull 发明)
    • 线性空间复杂度:O(width × height)
    • 与绘制顺序无关(任意顺序都能得到正确结果)
  3. 插值的意义

    • 从顶点属性平滑过渡到片段属性
    • 用于颜色、纹理坐标、法线、深度等
    • 是着色器编程的核心概念
  4. 边界框优化

    • 避免遍历整个屏幕(800×600 = 480,000 像素)
    • 小三角形可能只需检查几百个像素
    • 性能提升可达 10-100 倍

遇到的坑和解决方案

问题:编译错误 - numeric_limits 找不到

1
error: 'numeric_limits' is not a member of 'std'

原因

1
std::vector<float> zbuffer(width * height, std::numeric_limits<float>::max());

使用了 std::numeric_limits 但没有包含 <limits> 头文件。

解决

1
#include <limits>  // 添加这行

教训:C++ 标准库的模板类需要显式包含对应头文件,不像 Python 那样自动导入。

性能分析

算法复杂度

  • 时间:O(N × A)
    • N = 三角形数量
    • A = 每个三角形的边界框面积(像素数)
  • 空间:O(W × H)
    • W × H = 屏幕分辨率

本例

  • 屏幕:800×600 = 480,000 像素
  • 3个三角形,每个约 10,000-50,000 像素
  • 总检查次数:约 100,000 次重心坐标计算
  • 运行时间:< 0.1 秒(未优化的 C++ 代码)

可能的优化

  1. SIMD 向量化 - SSE/AVX 加速重心坐标计算
  2. 多线程 - 按扫描线或tile并行
  3. Early-Z - 提前深度测试,减少不必要的插值
  4. Hierarchical Z-Buffer - 多层次深度缓冲

扩展方向

这个基础光栅化器是现代GPU渲染管线的简化版本。后续可以扩展:

  1. 透视校正纹理映射 - 除以 w 分量修正透视失真
  2. 抗锯齿(Anti-Aliasing) - MSAA / SSAA / FXAA
  3. 背面剔除(Back-face Culling) - 只渲染朝向相机的三角形
  4. 顶点着色器 + 片段着色器 - 可编程渲染管线
  5. 延迟渲染(Deferred Rendering) - G-Buffer 方法

代码仓库

GitHub: daily-coding-practice/2026/02/02-26-Triangle-Rasterization

参考资料


完成时间: 2026-02-26 05:41
迭代次数: 2 次
编译器: g++ (C++11)
总耗时: 11 分钟

下一步计划

  • B-spline 曲线
  • BVH 加速结构
  • 抗锯齿技术(MSAA/FXAA)