上一讲的投影变换把场景压进 NDC 的立方体, 都落在 。屏幕由离散像素组成,光栅化要把这些连续坐标落到像素网格上,再决定每个像素显示什么颜色。
像素坐标
屏幕宽 、高 ,像素 覆盖 ,中心位于 ,索引范围是 ,。屏幕范围是 ,原点在左下角,这是 GAMES101 的约定;图像文件通常把原点放在左上角,读图时 需要翻转一次。
从 NDC 到屏幕的映射称为视口变换,它是一次缩放加一次平移:
暂时保持 ,深度测试时再处理。代入边界值可以检查映射: 得到 , 得到 ;像素中心是 ,整数坐标落在像素边界上,不能当作采样位置。
像素有面积,判断“这个像素是否被三角形覆盖”需要选一个代表点。本文统一使用像素中心,这个选择会在边缘处引出走样。
三角形光栅化
光栅化要回答的问题是:给定屏幕空间中的三个顶点,哪些像素被这个三角形覆盖。选择三角形的原因是它具备一组方便的性质:三个点确定一个平面,三角形总是凸的,内部和外部的定义没有歧义,任意多边形都可以拆成三角形。
判断点 是否在三角形内,可以用三条有向边的叉乘。对边 ,计算 :
对三条边分别计算 、、。叉乘是一个标量,符号表示 在有向边 的左侧还是右侧;三角形是凸的, 同时在三条边的同一侧时,它就在三角形内部。
若 按逆时针排列,三个叉乘都非负表示 在内部;按顺时针排列时三个叉乘都非正。实现时判断三个符号是否一致即可,不用死记方向。叉乘等于 表示点落在边上,共享边需要约定归属,否则相邻三角形会把同一个像素画两次或漏掉;工业界用 top-left 规则解决:落在上边或左边上的点归当前三角形,落在下边或右边上的点归相邻三角形,共享边只会被覆盖一次。
浮点误差也会让边界判断不稳定,实际实现通常加一个很小的容差,或者使用整数坐标。直接遍历整个屏幕的代价太高,可以先取三角形在屏幕上的包围盒,只测试盒内的像素;叉乘还可以逐行增量更新,避免每个像素都重新算乘法。
每个像素只测一个中心点,结果是二值的:在三角形内就填充,不在就留空。
图中有些采样点正好落在三角形边上,这也是需要边界规则的原因。二值化的结果在边缘处形成台阶;把这一步看成对连续三角形的点采样,就能理解台阶来自采样过程,三角形本身是光滑的。
走样
像素中心采样只取一个点,三角形边缘的细节被丢掉了。走样(aliasing)描述的就是这类由采样不足造成的错误,要解释它,需要先把图像看成信号。
图像是信号
灰度图可以写成亮度函数 ,彩色图是红绿蓝三个通道各一个函数。频率描述亮度变化的快慢:单位距离内变化次数越多,频率越高;平滑区域是低频,边缘、纹理和噪声是高频。
傅里叶变换把任意信号拆成不同频率、不同方向的正弦波,频谱给出每个频率分量的振幅。图像有 两个方向,二维变换可以看成先沿行做一维变换,再沿列做一维变换;一维声音信号把时间作为自变量,图像把位置作为自变量,频率的含义相同,都是单位自变量内变化的次数。
频谱图怎么看
频谱图通常把零频放在中心。中心代表整张图的平均亮度,离中心越远频率越高;横轴是水平方向的频率,纵轴是垂直方向的频率。图中一个点的亮度表示该频率分量的强度,与原图对应位置的亮度无关;频谱一般显示对数幅度,否则中心会过曝,外围的高频细节看不见。
读图时可以记一个对应关系:竖条纹沿 方向变化快,亮点落在横轴上;横条纹沿 方向变化快,亮点落在纵轴上。
穿过中心的十字线来自图像的有限边界。图像只在矩形区域内非零,相当于理想图像乘了一个矩形窗:左右两条竖直边界让亮度在 方向突然截断,频谱沿横轴铺开;上下两条水平边界让亮度在 方向突然截断,频谱沿纵轴铺开。矩形窗的频谱沿两个主轴衰减最慢,所以十字最亮;照片四周的边框加强了这种突变。中心亮说明图像以低频为主,外围的亮点对应细节、边缘和噪声。
低通与高通
滤波是在频域乘一个掩膜,保留哪些频率,就保留哪些图像内容。低通保留中心、去掉外围,结果是模糊,平滑会抹掉细节和噪声,极端情况下只剩直流分量,整张图变成平均颜色;高通去掉中心、保留外围,结果是边缘和轮廓,平坦区域没有高频,变成黑色。
低通图的右边只剩一个白点,表示保留了很窄的低频;高通图的右边有一个黑盘,表示低频被挖掉。这两张图把“频率”与图像内容对应起来:低频是缓慢的明暗变化,高频是锐利的边和纹理。
卷积就是加权平均
卷积核是一个小矩阵,输出像素等于邻域像素按核权重的加权和。 全 的核就是九宫格平均;平均会抵消相邻像素之间的快速变化,保留缓慢的趋势,所以均值滤波是低通。
卷积定理把空域和频域连起来:空域卷积对应频域相乘,空域相乘对应频域卷积。
| 空域(图像) | 频域(频谱) |
|---|---|
| 卷积 | 相乘 |
| 相乘 | 卷积 |
滤波用第一行:图像与核卷积,等于频谱乘上核的频谱;均值核的频谱集中在低频,相乘后外围高频被压低。采样用第二行:图像乘梳状函数,等于频谱与梳状函数卷积,结果是频谱复制。均值核不是理想低通,它的频谱有旁瓣,但主瓣在低频,足以起到模糊作用。
图的上排是空域卷积,图像与 的均值核作用后变模糊;下排是频域相乘,图像频谱乘上核的频谱,外围高频被压低。
采样与频谱复制
采样是在离散网格点上取值,等价于把连续图像乘上一个梳状函数,梳齿间隔等于像素间距。由卷积定理,空域相乘对应频域卷积;梳状函数的频谱还是梳状函数,间隔为 ,其中 是像素间距,采样后的频谱就是原频谱以 为周期复制。
图 (a) 到 (f) 展示了这条链:连续信号、带限频谱、梳状函数、梳状频谱、采样信号、复制后的频谱。如果 ,复制之间不重叠,用一个低通取回中心那一份就能重建原信号,这个条件称为 Nyquist 条件;如果 ,复制重叠,重叠区的高频混进低频,无法再分开。
落到图像的具体数字:像素间距为 , 周期/像素,Nyquist 频率是 周期/像素,比两个像素还细的条纹无法表示。
走样的来源
频谱重叠是走样的直接原因。图的上排是密集采样,复制之间分离;下排是稀疏采样,复制重叠,圈出的部分就是混叠。
空域里也能看到同样的现象:高频正弦和低频正弦在同样的采样点上给出相同样本,只看样本,无法判断原来是谁。
渲染中的边缘亮度变化很突然,包含大量高频。像素中心点采样只取一个值,边缘就变成台阶;远处的棋盘格和细纹理会产生摩尔纹。
左边是细密条纹被像素网格采样后产生的摩尔纹,右边是三角形边缘的锯齿,两种现象来自同一个原因。时间上也有走样:车轮倒转、视频里的条纹,采样率不够时高频运动被看成低频运动。走样发生在采样那一刻,信息已经混在一起,后处理无法把它分开。
先滤波,再采样
最直接的想法是采样之后再模糊,但顺序错了。采样已经把高频折叠进低频,模糊只能把混叠结果抹匀。
正确做法是在采样前低通,把最高频率压到 以下,再做稀疏采样;变窄的频谱不会再与相邻复制重叠。
落到像素上,正确值是三角形覆盖该像素的面积比例乘以颜色;中心点是否在三角形内只是一个二值近似。像素面积平均就是用一个盒式滤波器做预滤波。盒式滤波不是理想低通,但它是像素的物理模型;提高分辨率能减轻锯齿,因为 变大,Nyquist 频率变高,能保留更多高频,分辨率不可能无限提高,所以仍然需要抗锯齿。面积积分难算,MSAA 用多个子样本估计它。
抗锯齿
抗锯齿(anti-aliasing)也叫反走样,它的目标是在采样前去掉会混叠的高频,或者用更多样本逼近像素面积的平均颜色。
MSAA
把每个像素分成 个子样本,每个子样本做一次内外测试,覆盖率等于落在三角形内的子样本数除以 4。
颜色按覆盖率混合:设覆盖率为 ,颜色等于 乘三角形颜色加上 乘背景颜色,边缘像素不再是二值,台阶被压成渐变。这是对面积积分的离散近似:子样本越多,覆盖率越细,边缘越平滑。
MSAA 与 SSAA 的区别在着色次数:SSAA 的每个子样本都完整着色再平均;MSAA 的子样本主要记录覆盖和深度,颜色算一次,按覆盖率混合。MSAA 更便宜,但对纹理、高光内部的走样无效;它的代价是 的覆盖测试、深度存储和带宽,另加 resolve 步骤(把子样本合并成最终像素)。
其他方法
常见的其他方案如下表。
| 方法 | 做法 | 局限 |
|---|---|---|
| SSAA | 每个子样本都着色再平均 | 质量好,成本随样本数增长 |
| FXAA | 后处理检测边缘并模糊 | 便宜,没有几何信息,可能糊文字 |
| TAA | 每帧抖动采样位置,复用历史帧 | 需要运动向量,可能拖影 |
| DLSS | 低分辨率加抖动,再用神经网络重建 | 依赖硬件和训练,可能产生伪影 |
MSAA 用更多采样点逼近像素面积积分,属于“先滤波再采样”的近似。它不改变几何,也不提高真实分辨率。