什么是光栅化

简单说,光栅化(Rasterization)就是把3D的几何图形(比如三角形)转换成屏幕上的像素点。这是实时渲染的核心步骤,你玩的每一款3D游戏,背后都在疯狂地做光栅化。

为什么是三角形?因为三角形是最简单的多边形,三个点确定一个平面,不会出现凹多边形的问题。复杂的3D模型都是由成千上万个三角形组成的。

基本流程

光栅化的过程大致是这样的:

  1. 顶点处理:把3D坐标变换到屏幕坐标(MVP变换)
  2. 图元装配:把三个顶点组装成三角形
  3. 三角形遍历:找出哪些像素被这个三角形覆盖
  4. 片元处理:计算每个被覆盖像素的颜色
  5. 深度测试:决定哪个三角形在前面

坐标变换

3D物体从模型空间到屏幕需要经过几次变换:

模型空间 → 世界空间 → 观察空间 → 裁剪空间 → 屏幕空间
         Model矩阵    View矩阵    Projection矩阵   视口变换

Model矩阵:把物体放到世界中正确的位置、旋转、缩放。

View矩阵:相当于相机,决定了从哪个角度看。

Projection矩阵:透视投影,让远处的东西看起来小。这就是为什么铁轨远处会交汇。

三角形遍历

这是光栅化最核心的步骤。给定屏幕上的三角形,怎么判断哪些像素在三角形内部?

最常用的方法是边函数(Edge Function)。三角形的三条边把平面分成两半,如果一个点在三条边的同一侧,那它就在三角形内部。

// 判断点P是否在边AB的左侧
float edgeFunction(vec2 A, vec2 B, vec2 P) {
    return (P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x);
}

如果三个边函数的值都同号(都正或都负),点就在三角形内部。

深度测试

一个像素可能被多个三角形覆盖(比如前面的物体挡住了后面的)。深度测试通过比较Z值来决定哪个三角形"赢"。

通常使用Z-Buffer(深度缓冲):为每个像素存储一个深度值,新的片元只有Z值更小(更近)才能写入。

// 伪代码
if (fragment.depth < zbuffer[x][y]) {
    zbuffer[x][y] = fragment.depth;
    framebuffer[x][y] = fragment.color;
}

插值

三角形内部的像素需要计算颜色、纹理坐标等属性。这些属性在三个顶点上已知,内部的值通过重心坐标插值得到。

简单说就是:离哪个顶点近,就更偏向那个顶点的属性值。数学上用重心坐标(barycentric coordinates)来表示三角形内部任意点的位置。

和光线追踪的区别

光栅化是从物体出发,把物体投影到屏幕。光线追踪是从相机出发,发射光线看碰到什么。

现在GPU有了RT Core,实时光线追踪也开始普及了。但光栅化仍然是基础,大部分游戏还是以光栅化为主,光线追踪为辅。

学习资源推荐

图形渲染入门的资料推荐:

LearnOpenGL是入门首选,赵新政的教程适合中文用户快速上手。GAMES101偏理论但讲得最深入。三个配合着看效果最好。