skip to content

Search

光栅化

17 min read Updated:
四季与古树

从 NDC 到像素坐标与三角形覆盖测试,用频谱、卷积和采样解释走样,并用 MSAA 抗锯齿

上一讲的投影变换把场景压进 NDC 的立方体,x,yx,y 都落在 [−1,1][-1,1]。屏幕由离散像素组成,光栅化要把这些连续坐标落到像素网格上,再决定每个像素显示什么颜色。

像素坐标

屏幕宽 ww、高 hh,像素 (i,j)(i,j) 覆盖 [i,i+1]×[j,j+1][i,i+1]\times[j,j+1],中心位于 (i+0.5,j+0.5)(i+0.5,j+0.5),索引范围是 i=0,…,w−1i=0,\dots,w-1,j=0,…,h−1j=0,\dots,h-1。屏幕范围是 [0,w]×[0,h][0,w]\times[0,h],原点在左下角,这是 GAMES101 的约定;图像文件通常把原点放在左上角,读图时 yy 需要翻转一次。

屏幕像素阵列与像素中心坐标

从 NDC 到屏幕的映射称为视口变换,它是一次缩放加一次平移:

xs=w2(xndc+1),ys=h2(yndc+1)x_s=\frac{w}{2}(x_{ndc}+1),\qquad y_s=\frac{h}{2}(y_{ndc}+1)

zz 暂时保持 [−1,1][-1,1],深度测试时再处理。代入边界值可以检查映射:xndc=−1x_{ndc}=-1 得到 00,xndc=1x_{ndc}=1 得到 ww;像素中心是 0.5,1.5,…,w−0.50.5,1.5,\dots,w-0.5,整数坐标落在像素边界上,不能当作采样位置。

像素有面积,判断“这个像素是否被三角形覆盖”需要选一个代表点。本文统一使用像素中心,这个选择会在边缘处引出走样。

三角形光栅化

光栅化要回答的问题是:给定屏幕空间中的三个顶点,哪些像素被这个三角形覆盖。选择三角形的原因是它具备一组方便的性质:三个点确定一个平面,三角形总是凸的,内部和外部的定义没有歧义,任意多边形都可以拆成三角形。

判断点 PP 是否在三角形内,可以用三条有向边的叉乘。对边 ABAB,计算 AB⃗×AP⃗\vec{AB}\times\vec{AP}:

AB⃗×AP⃗=(Bx−Ax)(Py−Ay)−(By−Ay)(Px−Ax)\vec{AB}\times\vec{AP} =(B_x-A_x)(P_y-A_y)-(B_y-A_y)(P_x-A_x)

对三条边分别计算 AB⃗×AP⃗\vec{AB}\times\vec{AP}、BC⃗×BP⃗\vec{BC}\times\vec{BP}、CA⃗×CP⃗\vec{CA}\times\vec{CP}。叉乘是一个标量,符号表示 PP 在有向边 ABAB 的左侧还是右侧;三角形是凸的,PP 同时在三条边的同一侧时,它就在三角形内部。

若 A,B,CA,B,C 按逆时针排列,三个叉乘都非负表示 PP 在内部;按顺时针排列时三个叉乘都非正。实现时判断三个符号是否一致即可,不用死记方向。叉乘等于 00 表示点落在边上,共享边需要约定归属,否则相邻三角形会把同一个像素画两次或漏掉;工业界用 top-left 规则解决:落在上边或左边上的点归当前三角形,落在下边或右边上的点归相邻三角形,共享边只会被覆盖一次。

浮点误差也会让边界判断不稳定,实际实现通常加一个很小的容差,或者使用整数坐标。直接遍历整个屏幕的代价太高,可以先取三角形在屏幕上的包围盒,只测试盒内的像素;叉乘还可以逐行增量更新,避免每个像素都重新算乘法。

每个像素只测一个中心点,结果是二值的:在三角形内就填充,不在就留空。

三角形覆盖的像素中心采样点 按采样结果填充后的三角形像素

图中有些采样点正好落在三角形边上,这也是需要边界规则的原因。二值化的结果在边缘处形成台阶;把这一步看成对连续三角形的点采样,就能理解台阶来自采样过程,三角形本身是光滑的。

走样

像素中心采样只取一个点,三角形边缘的细节被丢掉了。走样(aliasing)描述的就是这类由采样不足造成的错误,要解释它,需要先把图像看成信号。

图像是信号

灰度图可以写成亮度函数 f(x,y)f(x,y),彩色图是红绿蓝三个通道各一个函数。频率描述亮度变化的快慢:单位距离内变化次数越多,频率越高;平滑区域是低频,边缘、纹理和噪声是高频。

傅里叶变换把任意信号拆成不同频率、不同方向的正弦波,频谱给出每个频率分量的振幅。图像有 x,yx,y 两个方向,二维变换可以看成先沿行做一维变换,再沿列做一维变换;一维声音信号把时间作为自变量,图像把位置作为自变量,频率的含义相同,都是单位自变量内变化的次数。

频谱图怎么看

频谱图通常把零频放在中心。中心代表整张图的平均亮度,离中心越远频率越高;横轴是水平方向的频率,纵轴是垂直方向的频率。图中一个点的亮度表示该频率分量的强度,与原图对应位置的亮度无关;频谱一般显示对数幅度,否则中心会过曝,外围的高频细节看不见。

读图时可以记一个对应关系:竖条纹沿 xx 方向变化快,亮点落在横轴上;横条纹沿 yy 方向变化快,亮点落在纵轴上。

灰度人像及其二维频谱,中心为低频

穿过中心的十字线来自图像的有限边界。图像只在矩形区域内非零,相当于理想图像乘了一个矩形窗:左右两条竖直边界让亮度在 xx 方向突然截断,频谱沿横轴铺开;上下两条水平边界让亮度在 yy 方向突然截断,频谱沿纵轴铺开。矩形窗的频谱沿两个主轴衰减最慢,所以十字最亮;照片四周的边框加强了这种突变。中心亮说明图像以低频为主,外围的亮点对应细节、边缘和噪声。

低通与高通

滤波是在频域乘一个掩膜,保留哪些频率,就保留哪些图像内容。低通保留中心、去掉外围,结果是模糊,平滑会抹掉细节和噪声,极端情况下只剩直流分量,整张图变成平均颜色;高通去掉中心、保留外围,结果是边缘和轮廓,平坦区域没有高频,变成黑色。

低通滤波后的模糊图像与只保留低频的频谱 高通滤波后的边缘图像与去掉低频的频谱

低通图的右边只剩一个白点,表示保留了很窄的低频;高通图的右边有一个黑盘,表示低频被挖掉。这两张图把“频率”与图像内容对应起来:低频是缓慢的明暗变化,高频是锐利的边和纹理。

卷积就是加权平均

卷积核是一个小矩阵,输出像素等于邻域像素按核权重的加权和。3×33\times3 全 1/91/9 的核就是九宫格平均;平均会抵消相邻像素之间的快速变化,保留缓慢的趋势,所以均值滤波是低通。

卷积定理把空域和频域连起来:空域卷积对应频域相乘,空域相乘对应频域卷积。

空域(图像)频域(频谱)
卷积相乘
相乘卷积

滤波用第一行:图像与核卷积,等于频谱乘上核的频谱;均值核的频谱集中在低频,相乘后外围高频被压低。采样用第二行:图像乘梳状函数,等于频谱与梳状函数卷积,结果是频谱复制。均值核不是理想低通,它的频谱有旁瓣,但主瓣在低频,足以起到模糊作用。

空域 3×3 均值卷积与频域相乘的对应关系

图的上排是空域卷积,图像与 1/91/9 的均值核作用后变模糊;下排是频域相乘,图像频谱乘上核的频谱,外围高频被压低。

采样与频谱复制

采样是在离散网格点上取值,等价于把连续图像乘上一个梳状函数,梳齿间隔等于像素间距。由卷积定理,空域相乘对应频域卷积;梳状函数的频谱还是梳状函数,间隔为 fs=1/df_s=1/d,其中 dd 是像素间距,采样后的频谱就是原频谱以 fsf_s 为周期复制。

一维信号采样后频谱以采样频率为周期复制

图 (a) 到 (f) 展示了这条链:连续信号、带限频谱、梳状函数、梳状频谱、采样信号、复制后的频谱。如果 fs≥2fmax⁡f_s\ge 2f_{\max},复制之间不重叠,用一个低通取回中心那一份就能重建原信号,这个条件称为 Nyquist 条件;如果 fs<2fmax⁡f_s<2f_{\max},复制重叠,重叠区的高频混进低频,无法再分开。

落到图像的具体数字:像素间距为 11,fs=1f_s=1 周期/像素,Nyquist 频率是 0.50.5 周期/像素,比两个像素还细的条纹无法表示。

走样的来源

频谱重叠是走样的直接原因。图的上排是密集采样,复制之间分离;下排是稀疏采样,复制重叠,圈出的部分就是混叠。

密集采样与稀疏采样的频谱,稀疏采样发生混叠

空域里也能看到同样的现象:高频正弦和低频正弦在同样的采样点上给出相同样本,只看样本,无法判断原来是谁。

高频正弦与低频正弦在同样的采样点上得到相同样本

渲染中的边缘亮度变化很突然,包含大量高频。像素中心点采样只取一个值,边缘就变成台阶;远处的棋盘格和细纹理会产生摩尔纹。

摩尔纹与三角形边缘的锯齿

左边是细密条纹被像素网格采样后产生的摩尔纹,右边是三角形边缘的锯齿,两种现象来自同一个原因。时间上也有走样:车轮倒转、视频里的条纹,采样率不够时高频运动被看成低频运动。走样发生在采样那一刻,信息已经混在一起,后处理无法把它分开。

先滤波,再采样

最直接的想法是采样之后再模糊,但顺序错了。采样已经把高频折叠进低频,模糊只能把混叠结果抹匀。

先采样后滤波留下摩尔纹,先滤波后采样得到平滑结果

正确做法是在采样前低通,把最高频率压到 fs/2f_s/2 以下,再做稀疏采样;变窄的频谱不会再与相邻复制重叠。

先低通缩小频谱再稀疏采样,避免混叠

落到像素上,正确值是三角形覆盖该像素的面积比例乘以颜色;中心点是否在三角形内只是一个二值近似。像素面积平均就是用一个盒式滤波器做预滤波。盒式滤波不是理想低通,但它是像素的物理模型;提高分辨率能减轻锯齿,因为 fsf_s 变大,Nyquist 频率变高,能保留更多高频,分辨率不可能无限提高,所以仍然需要抗锯齿。面积积分难算,MSAA 用多个子样本估计它。

抗锯齿

抗锯齿(anti-aliasing)也叫反走样,它的目标是在采样前去掉会混叠的高频,或者用更多样本逼近像素面积的平均颜色。

MSAA

把每个像素分成 2×22\times2 个子样本,每个子样本做一次内外测试,覆盖率等于落在三角形内的子样本数除以 4。

每个像素内 2×2 个子采样点与三角形覆盖情况 每个像素按覆盖比例得到 25% 到 100% 的颜色

颜色按覆盖率混合:设覆盖率为 cc,颜色等于 cc 乘三角形颜色加上 (1−c)(1-c) 乘背景颜色,边缘像素不再是二值,台阶被压成渐变。这是对面积积分的离散近似:子样本越多,覆盖率越细,边缘越平滑。

MSAA 与 SSAA 的区别在着色次数:SSAA 的每个子样本都完整着色再平均;MSAA 的子样本主要记录覆盖和深度,颜色算一次,按覆盖率混合。MSAA 更便宜,但对纹理、高光内部的走样无效;它的代价是 4×4\times 的覆盖测试、深度存储和带宽,另加 resolve 步骤(把子样本合并成最终像素)。

其他方法

常见的其他方案如下表。

方法做法局限
SSAA每个子样本都着色再平均质量好,成本随样本数增长
FXAA后处理检测边缘并模糊便宜,没有几何信息,可能糊文字
TAA每帧抖动采样位置,复用历史帧需要运动向量,可能拖影
DLSS低分辨率加抖动,再用神经网络重建依赖硬件和训练,可能产生伪影

MSAA 用更多采样点逼近像素面积积分,属于“先滤波再采样”的近似。它不改变几何,也不提高真实分辨率。