抗锯齿算法
需要预先学习:
1. 抗锯齿的分类
光栅化 的结尾简单列过几种反走样技术的名字,这篇笔记把它们逐个展开,讲清每种算法在管线的哪个位置工作、付出什么代价、换来什么样的边缘质量。
先回到问题的根源。几何边缘之所以产生锯齿,是因为"三角形边界"在数学上是一个无限陡的阶跃信号(step signal,从一个值瞬间跳到另一个值)——非黑即白,频率无穷高。任何有限的采样率都追不上它(奈奎斯特定理,见 光栅化),所以抗锯齿算法没有谁能"完全消除"锯齿,它们都在做同一件事:近似边缘在像素内的覆盖率。一个像素 70% 被三角形覆盖,理想的输出就是 70% 的三角形颜色混合 30% 的背景色,各种算法的差别只在"怎么知道是 70%"。
按"额外的采样从哪里来",主流算法分三类:
| 类别 | 思路 | 代表算法 |
|---|---|---|
| 超采样(supersampling) | 增加每个像素的采样点数 | SSAA、MSAA |
| 后处理(post-process) | 从渲染好的图像里检测并修复边缘 | FXAA、MLAA、SMAA |
| 时间复用(temporal) | 把采样点分散到多帧累积 | TAA、TAAU |
2. SSAA:不聪明但正确
SSAA(Super-Sampling Anti-Aliasing,超采样抗锯齿)是最直接的想法:一个采样点不够,那就多用几个。把整个场景渲染到 倍于屏幕分辨率的离屏缓冲上(4x SSAA 就是 倍分辨率,每像素 4 个样本),渲染完成后把 个样本平均成一个像素,这一步称为解析(resolve):
这个平均本质上是一次低通滤波(盒式滤波器,见 光栅化 的卷积一节),正好符合"先模糊、再采样"的反走样理论。
SSAA 的特点:
- 对所有走样都有效:几何边缘、纹理摩尔纹、shader 里的高频计算(掠射角高光、程序化纹理)全部受益,因为它老老实实提高了每处的采样密度;
- 所有开销无差别放大:片元着色、显存、带宽全部乘以 。4x SSAA 就是 4 倍分辨率的渲染成本,对管线的每一个阶段都一视同仁——包括那些根本不产生走样的像素内部。
观察一下就会发现浪费:走样只发生在几何边缘,屏幕上 95% 以上的像素都在三角形内部,那里的 4 个样本颜色完全相同,却都被完整着色了一遍。MSAA 就是从这个观察出发的。
3. MSAA:只对边缘多重采样
MSAA(Multisample Anti-Aliasing,多重采样抗锯齿)的核心改进:把"覆盖测试"与"着色"分开。
- 每个像素内布置 个子采样点(subsample,4x 通常是旋转网格布局,即一个正方形旋转 45°,对斜向边缘的覆盖率估计更均匀);
- 光栅化时,对每个子采样点做"是否在三角形内"的测试,得到该像素的覆盖率(coverage,如 4 个子采样点中 3 个被覆盖,覆盖率就是 3/4);每个子采样点还各自记录深度,参与深度测试;
- 片元着色器每个像素只执行一次,无论覆盖率是多少;
- resolve 时按覆盖率加权混合:三角形覆盖 3/4,像素颜色就是 75% 的三角形颜色加 25% 的背景色。
其中 是被覆盖的子采样点数。于是:
| 情况 | 着色次数 | 覆盖测试 | 效果 |
|---|---|---|---|
| 像素完全在三角形内 | 1 | 全部通过 | 与不开 MSAA 相同 |
| 像素跨越边缘 | 1 | 部分通过 | 边缘平滑 |
| 像素完全在外 | 0 | 全部失败 | 直接丢弃 |
片元着色成本几乎不变,额外付出的只是子采样点的覆盖测试与存储(深度也按样本存储),这就是 MSAA 性价比高的原因,也是它成为硬件标准能力的原因。
3.1 MSAA 管不到的地方
MSAA 的覆盖率只描述几何边缘,两种走样它无能为力:
- 着色内部的高频:alpha test 的镂空边缘、掠射角的高光锯齿、shader 程序化纹理的摩尔纹——这些在像素内部就剧烈变化,一个像素一次着色的采样密度不够;
- 纹理走样:靠 mipmap 与各向异性过滤解决(见 纹理映射),与 MSAA 无关。
3.2 CSAA 与 EQAA:存储的进一步压缩
CSAA(Coverage Sampling Anti-Aliasing,NVIDIA)与 EQAA(Enhanced Quality Anti-Aliasing,AMD)是 MSAA 的存储优化版:覆盖率判定用 8 个甚至 16 个子采样点,但颜色与深度只存 4 份。覆盖率的精度更高(边缘更平滑),存储成本保持 4x。思路是"边缘斜率本来变化平缓,覆盖率用高精度估计、颜色用低精度存储就够"。
3.3 延迟渲染与 MSAA 的矛盾
MSAA 依赖光栅化阶段的覆盖率信息,而延迟渲染把几何属性烘焙进 G-Buffer、着色推迟到全屏 pass(见 前向渲染与延迟渲染),覆盖率信息在光照阶段已经丢失。所以延迟管线基本放弃 MSAA,转用本篇后面的后处理或时间性方案——这是"延迟渲染代价"一节中"MSAA 困难"的具体原因。
4. FXAA 与形态学抗锯齿:后处理
后处理抗锯齿(post-process AA)的共同框架:图像渲染完成后,把它当作一张普通的图来修复。不需要几何信息、不需要多采样,管线改动最小,对前向、延迟管线一视同仁。
4.1 FXAA
FXAA(Fast Approximate Anti-Aliasing,快速近似抗锯齿)单 pass 工作,步骤大致是:
- 对每个像素计算局部对比度(用亮度的最大差值),超过阈值判定为边缘像素;
- 沿对比度更大的方向(水平或垂直)搜索局部最小最大亮度,确定边缘的两个端点,从而估计边缘的走向;
- 把像素相对理想边缘的位置换算成混合系数,向两侧的端点颜色做定向模糊——只沿边缘法向混合,不沿边缘方向糊。
FXAA 的速度极快(一次纹理采样为主),质量取决于阈值与搜索范围。根本局限:它只看颜色,无法区分"几何边缘"与"高频细节"——远处的纹理、细铁丝、文字统统被当作边缘糊掉,画面偏"肉"。
4.2 MLAA 与 SMAA:形态学方法
形态学抗锯齿(morphological AA)更进一步:不只模糊,而是重建边缘的几何形状。
- MLAA(Morphological Anti-Aliasing):从二值化的边缘图像中识别出 U、L、Z、S 等基本边缘形状,反推出理想直线穿过像素的位置,按精确的覆盖面积混合颜色;
- SMAA(Subpixel Morphological Anti-Aliasing):MLAA 的增强版,输入不是粗糙的二值边缘,而是预计算的边缘检测纹理(每个像素给出到最近边缘的距离与走向),并利用子像素信息与局部对比度自适应,质量明显好于 FXAA,还提供了结合时间采样的 SMAA T2x 变体。
形态学方法的输入通常是颜色图像(或辅助的边缘缓冲),仍然会被相似颜色的细节欺骗,但它对"理想几何边缘"的重建已经非常接近 MSAA 的效果,而成本只是几个后处理 pass。
注:后处理方法修复的是已经走样的图像,信息已经丢失,本质是"猜测"边缘原本的样子;超采样方法是在采样阶段保留更多信息。这是两类方法质量的根本分界。
5. TAA:把采样分散到时间
单帧内增加采样太贵,那就少采一点,但每帧换一个位置采,多帧攒起来等效于大量采样点——这就是 TAA(Temporal Anti-Aliasing,时间性抗锯齿)的思路。它由三个机制组成。
5.1 抖动(Jitter)
每帧给投影矩阵加一个小于半像素的平移偏移,让所有采样点在像素内逐帧移动:
偏移序列通常用低差异序列(low-discrepancy sequence,一种在超立方体内"尽可能均匀"分布的确定性序列),例如 Halton(2,3) 序列:把帧号 分别写成 2 进制和 3 进制,各位数字反过来放到小数点后,得到 内的一个点。这样前 帧的采样点在像素内近似均匀铺开,等效于 SSAA 的多采样布局——只是摊在了时间上。
5.2 重投影(Reprojection)
历史帧的画面不能直接拿来混合:相机和物体都动了,上一帧的像素对应世界的另一个位置。重投影用运动矢量(motion vector,通常在顶点着色阶段由当前帧与上一帧的视图投影矩阵差算出)回答"这个片元在上一帧屏幕的哪里",到历史颜色缓冲的对应位置采样,得到历史颜色 。
5.3 历史混合
混合系数 通常取 0.05 ~ 0.2:新帧占一小部分,历史占大部分,多帧累积后每个像素等效收集了几十个不同位置的样本。
5.4 两大瑕疵与对策
TAA 的麻烦全在"历史不一定可靠":
- 鬼影(ghosting,移动物体后面拖出的残影):重投影出错时(遮挡关系变化、运动矢量不精确、粒子无深度),采到的历史颜色属于另一个物体。对策是邻域截断(neighborhood clamping):用当前帧 3×3 邻域的颜色包围盒(或最小/最大值)夹住历史颜色——历史色跑出"当前帧局部可能的颜色范围"就拉回来,鬼影被压制成轻微模糊;
- 闪烁(flickering,高频细节逐帧明暗跳动):细节丰富的像素(植被、铁丝网)每帧采样位置不同、clamp 范围又在变,累积结果不稳定。对策包括在更合适的颜色空间(如 YCoCg)做截断、按局部方差自适应收缩包围盒等。
这些修补手段是各引擎 TAA 实现差异的主要来源——同一算法,调参水平不同,观感天差地别。
5.5 不只是抗锯齿
TAA 的逐帧抖动采样是整个管线的时间采样框架:阴影、SSAO、掠射反射等一切需要多次采样的效果,都可以每帧只采一个样本、靠历史累积收敛。现代管线普遍内建 TAA,更多是为了这个"免费的时间累积",抗锯齿反而是顺带的。
6. TAAU:TAA + 上采样
TAAU(Temporal Anti-Aliasing Upscaling,时间性抗锯齿上采样),U 是 Upscaling(上采样)——它不是新的抗锯齿算法,而是 TAA 的一个变体:在低于输出分辨率的"内部分辨率"上渲染,同时把时间累积的结果升到全分辨率。
朴素的做法"低分辨率渲染 + 双线性放大"会严重糊掉细节,因为细节在低分辨率渲染时就已经丢失。TAAU 的关键区别在于历史累积发生在全分辨率:内部渲染虽然每帧只有 0.7x 的像素,但采样点逐帧抖动 + 重投影累积,多帧合起来的等效采样密度仍然覆盖了整个全分辨率像素。锯齿与细节由时间维度补齐,渲染成本却按内部比例降低:
| 方案 | 渲染分辨率 | 历史缓冲 | 细节恢复来源 |
|---|---|---|---|
| TAA | 输出分辨率 | 输出分辨率 | 时间累积 |
| 低分辨率 + 放大 | 内部(如 0.5x) | 无 | 无(不可恢复) |
| TAAU | 内部(如 0.67x) | 输出分辨率 | 时间累积 |
现代引擎与硬件方案几乎都建立在这个框架上:
- TAAU:虚幻引擎 4.22+ 的实现,固定管线、无可学习参数;
- TSR(Temporal Super Resolution):虚幻 5 的默认时间超分,对动态细节做了更精细的处理;
- DLSS(Deep Learning Super Sampling):把"重投影 + 累积"换成神经网络,由训练好的模型从低分辨率帧与运动矢量中重建高分辨率图像;
- FSR 2.x / XeSS:同样是时间超分家族,FSR 2 用传统算法,XeSS 用 AI 网络(可跑在 Intel 的 XMX 或通用 DP4a 指令上)。
注意:FSR 1.0 不属于这一家族——它是纯空间算法(edge-adaptive upscaling,只用当前帧信息做边自适应放大 + 锐化),没有任何时间累积,也不解决走样。判断一个"超分"方案的成色,先看它是否利用历史帧。
TAAU 家族的共同代价:一切依赖时间累积的效果在快速运动时退化(运动矢量覆盖不了的运动:粒子、透明物、镜头快切),静止画面最好、一动就糊是它们的通病。
7. 对比与选择
| 算法 | 类别 | 管线位置 | 解决什么 | 主要代价 | 主要缺陷 |
|---|---|---|---|---|---|
| SSAA | 超采样 | 光栅化 | 一切走样 | 全管线成本 ×N | 太贵 |
| MSAA | 超采样 | 光栅化 | 几何边缘 | 样本存储与带宽 | 管不到着色内部;延迟管线难用 |
| CSAA / EQAA | 超采样 | 光栅化 | 几何边缘 | 低于 MSAA | 同 MSAA |
| FXAA | 后处理 | 像素后 | 颜色边缘 | 极低 | 糊细节 |
| SMAA | 后处理 | 像素后 | 颜色边缘 | 几个 pass | 仍依赖边缘检测的猜测 |
| TAA | 时间 | 逐帧累积 | 几何 + 着色 | 运动矢量、历史缓冲 | 鬼影、闪烁 |
| TAAU | 时间 + 超分 | 低分辨率渲染 + 累积 | 几何 + 着色 + 分辨率 | 实现复杂 | 运动时细节退化 |
选择的实际经验:
- 移动端 / 低端设备:前向管线 + MSAA(4x),硬件直接支持,质量稳定;
- 桌面 3A / 延迟管线:TAA 家族是默认选择,抗锯齿、时间累积、超分三合一;性能不够时降 render scale 走 TAAU;
- VR:对延迟极其敏感、时间累积又难做,倾向 MSAA + 高刷新率;
- 风格化 / 像素风:往往故意保留锯齿,或只用 FXAA 最低限度处理。
注:这些算法并不互斥。一个现代管线完全可以 MSAA(几何边缘)+ mipmap(纹理)+ TAA(着色与累积)各管一段——它们针对的本来就是走样的不同来源。


