跳转至

光栅化

光栅化(Rasterization)来自德语单词“Raster”,意为栅格、网格。在计算机图形学中,光栅化是指将几何图元(如线段、多边形等)转换为屏幕上的像素的过程。

像三角形这样的构成几何体的基本形状单元被称为图元(Primitive),由顶点着色器输出的顶点数据组成;而光栅化阶段生成的用于填充屏幕像素的中间数据单位,则称为片元(Fragment),每个片元包含了坐标、颜色、深度等信息。

三角形光栅化

光栅化的输入是三角形顶点在屏幕上的投影位置,输出是三角形覆盖的像素,最终将连续的三角形片段转换为离散的像素。

三角形光栅化最简单的方法就是点采样,即遍历每个像素,判断像素中心是否在三角形内部,如果在则将像素填充为三角形的颜色。点采样一方面可以使用包围盒(Bounding Box)来减少需要检查的像素数量,另一方面可以使用以下方法来判断像素是否在三角形内部:

  • 向量叉乘:对于三角形的三个顶点 \(A, B, C\) 和点 \(P\),若 \(AP \times AB, BP \times BC, CP \times CA\) 同号,则说明点 \(P\) 在三条线段同一侧,即在三角形内部
  • 重心坐标:对于三角形的三个顶点 \(A, B, C\) 和点 \(P\),若 \(P\) 可以表示为 \(P = \alpha A + \beta B + \gamma C\),且 \(\alpha, \beta, \gamma \geq 0\)\(\alpha + \beta + \gamma = 1\),则说明点 \(P\) 在三角形内部

另一种光栅化算法是扫描线(Scanline)算法,它会先对三角形的三个顶点按 Y 坐标排序,然后从上到下扫描每一行像素,计算出每一行的左右交点,然后填充像素之间的颜色。扫描线算法的优点是可以处理任意多边形,但需要对多边形进行分解,且对于凹多边形需要特殊处理。

抗锯齿

空间上采样不足会出现锯齿(Jaggies),时间上采样不足会出现车轮错觉(Wagon-wheel effect),采样频率低于具有高频表面的物品会出现摩尔纹(Moire),这些现象被统称为采样失真(Sampling Artifacts),也被称为混叠(Aliasing),本意是在给定采样率下无法区分的两个不同的频率信号。

抗锯齿(Anti-aliasing)是一种在保持采样频率不变的情况下,减少采样失真的技术。常见的抗锯齿方法有:

  • 预滤波(Pre-filtering):在采样前先对信号进行滤波(如高斯滤波),去除高频成分,然后再进行采样
  • 超采样(Supersampling):在采样时将像素分成多个子像素,对每个子像素分别进行采样,然后取平均值,可以细分为:
    • 超级采样抗锯齿(Super-Sampling Anti-Aliasing,SSAA):直接在更高分辨率的图像上进行采样,然后缩小到目标分辨率,计算成本极高
    • 多重采样抗锯齿(Multi-Sampling Anti-Aliasing,MSAA):只对边缘的像素进行超采样,混合不同采样点的颜色
    • 时间性抗锯齿(Temporal Anti-Aliasing,TAA):每个像素只采样像素中心,将多帧的结果混合,通过连续帧之间的抖动来减少锯齿,在动态场景中可能会出现运动模糊的问题
    • 快速近似抗锯齿(Fast Approximate Anti-Aliasing,FXAA):对图像进行边缘检测,然后对边缘进行模糊处理,是一种后期处理的抗锯齿方法,计算成本非常低
    • 深度学习超采样(Deep Learning Super Sampling,DLSS):使用深度学习模型对图像进行超采样,可以在保持较低计算成本的情况下获得更好的效果

可见性与遮挡

画家算法

画家算法(Painter's Algorithm)是一种基于深度排序的渲染算法,主要用于处理遮挡问题。它的基本思路是将所有物体按照从远到近的顺序进行排序,然后从远到近依次绘制物体,这样可以确保近处的物体覆盖远处的物体。

具体而言,画家算法包括如下步骤:

  1. 将所有图元按照其深度进行排序,一般依据图元中心或最远点的 z 值进行比较
  2. 依照深度从大到小的顺序,依次将图元绘制到帧缓冲中
  3. 每次绘制时直接更新像素颜色,无需考虑已经存在的内容是否更近

画家算法的遮挡处理建立在图元之间可以完全排序的假设上。但事实上,并非所有图元之间都能建立全序关系。有时图元之间存在循环遮挡(Cyclic Overlap),也就是图元之间相互穿插遮挡,导致无法用一个线性顺序将其从远到近排列,这使得画家算法难以处理复杂的几何遮挡关系。

因为画家算法需要对所有图元进行排序,所以它的时间复杂度为 \(O(n \log n)\)

Z-buffer 算法

为克服画家算法在遮挡处理上的局限性,Z-buffer 算法被提出,并成为现代实时渲染流水线中广泛采用的标准做法。Z-buffer 算法的核心思想是在每个屏幕像素处维护一个与颜色缓冲区(frame buffer)并存的深度缓冲区(depth buffer),以记录每个像素位置上已渲染图元的最小深度值。随后渲染到该像素位置的每一个新的图元都要将其自身深度与 Z-buffer 中记录的深度值进行比较,仅当其更“靠近”观察者时,才允许其覆盖已有内容。

该算法的主要流程包括以下几个步骤:

  1. 初始化深度缓冲区,将所有值设置为投影空间中的最大深度
  2. 在图元光栅化生成片元阶段,计算每个片元在视图空间或投影空间下的深度值
  3. 对每个片元执行深度测试(Depth Test),与对应像素的当前 Z-buffer 值进行比较
  4. 若当前片元更靠近观察者,则更新颜色缓冲区和 Z-buffer,否则丢弃该片元

\(n\) 个图元进行渲染时,Z-buffer 算法的时间复杂度为 \(O(n)\)

Z-Fighting 与 Reverse Z-buffer

在标准透视投影下,Z-buffer 中的深度值与实际视线距离之间是非线性关系,使得绝大多数的深度精度集中在近平面附近,而远处深度值挤在接近 1 的小区间,精度很差。此时如果深度缓冲精度有限,在渲染深度相近的物体时,纹理会由于深度不停地切换而闪烁,这种问题称为 Z-Fighting(Z 冲突)。

为了解决 Z-Fighting 问题,可以使用 Reverse Z-buffer(反向 Z-buffer)技术。Reverse Z-buffer 的核心思想是将深度值的范围反转,使得近处物体的深度值更大,远处物体的深度值更小(如将远平面映射为 0,近平面映射为 1)。其原理是利用了 IEEE 754 浮点数的分布特性,即靠近 0 的值有更高的精度,而靠近 1 的值精度较低。