需要预先学习:


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,超采样抗锯齿)是最直接的想法:一个采样点不够,那就多用几个。把整个场景渲染到 NN 倍于屏幕分辨率的离屏缓冲上(4x SSAA 就是 2×22 \times 2 倍分辨率,每像素 4 个样本),渲染完成后把 NN 个样本平均成一个像素,这一步称为解析(resolve):

Cpixel=1N∑i=1NCiC_{pixel} = \frac{1}{N} \sum_{i=1}^{N} C_i

这个平均本质上是一次低通滤波(盒式滤波器,见 光栅化 的卷积一节),正好符合"先模糊、再采样"的反走样理论。

SSAA 的特点:

  • 对所有走样都有效:几何边缘、纹理摩尔纹、shader 里的高频计算(掠射角高光、程序化纹理)全部受益,因为它老老实实提高了每处的采样密度;
  • 所有开销无差别放大:片元着色、显存、带宽全部乘以 NN。4x SSAA 就是 4 倍分辨率的渲染成本,对管线的每一个阶段都一视同仁——包括那些根本不产生走样的像素内部。

观察一下就会发现浪费:走样只发生在几何边缘,屏幕上 95% 以上的像素都在三角形内部,那里的 4 个样本颜色完全相同,却都被完整着色了一遍。MSAA 就是从这个观察出发的。


3. MSAA:只对边缘多重采样

MSAA(Multisample Anti-Aliasing,多重采样抗锯齿)的核心改进:把"覆盖测试"与"着色"分开。

  • 每个像素内布置 NN 个子采样点(subsample,4x 通常是旋转网格布局,即一个正方形旋转 45°,对斜向边缘的覆盖率估计更均匀);
  • 光栅化时,对每个子采样点做"是否在三角形内"的测试,得到该像素的覆盖率(coverage,如 4 个子采样点中 3 个被覆盖,覆盖率就是 3/4);每个子采样点还各自记录深度,参与深度测试;
  • 片元着色器每个像素只执行一次,无论覆盖率是多少;
  • resolve 时按覆盖率加权混合:三角形覆盖 3/4,像素颜色就是 75% 的三角形颜色加 25% 的背景色。

Cpixel=cN⋅Ctri+(1−cN)⋅CbgC_{pixel} = \frac{c}{N} \cdot C_{tri} + \left(1 - \frac{c}{N}\right) \cdot C_{bg}

其中 cc 是被覆盖的子采样点数。于是:

情况 着色次数 覆盖测试 效果
像素完全在三角形内 1 全部通过 与不开 MSAA 相同
像素跨越边缘 1 部分通过 边缘平滑
像素完全在外 0 全部失败 直接丢弃

片元着色成本几乎不变,额外付出的只是子采样点的覆盖测试与存储(深度也按样本存储),这就是 MSAA 性价比高的原因,也是它成为硬件标准能力的原因。

3.1 MSAA 管不到的地方

MSAA 的覆盖率只描述几何边缘,两种走样它无能为力:

  1. 着色内部的高频:alpha test 的镂空边缘、掠射角的高光锯齿、shader 程序化纹理的摩尔纹——这些在像素内部就剧烈变化,一个像素一次着色的采样密度不够;
  2. 纹理走样:靠 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 工作,步骤大致是:

  1. 对每个像素计算局部对比度(用亮度的最大差值),超过阈值判定为边缘像素;
  2. 沿对比度更大的方向(水平或垂直)搜索局部最小最大亮度,确定边缘的两个端点,从而估计边缘的走向;
  3. 把像素相对理想边缘的位置换算成混合系数,向两侧的端点颜色做定向模糊——只沿边缘法向混合,不沿边缘方向糊。

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)

每帧给投影矩阵加一个小于半像素的平移偏移,让所有采样点在像素内逐帧移动:

offseti=Halton(i,2/3)−0.5\text{offset}_i = \text{Halton}(i, 2/3) - 0.5

偏移序列通常用低差异序列(low-discrepancy sequence,一种在超立方体内"尽可能均匀"分布的确定性序列),例如 Halton(2,3) 序列:把帧号 ii 分别写成 2 进制和 3 进制,各位数字反过来放到小数点后,得到 [0,1)2[0,1)^2 内的一个点。这样前 NN 帧的采样点在像素内近似均匀铺开,等效于 SSAA 的多采样布局——只是摊在了时间上。

5.2 重投影(Reprojection)

历史帧的画面不能直接拿来混合:相机和物体都动了,上一帧的像素对应世界的另一个位置。重投影用运动矢量(motion vector,通常在顶点着色阶段由当前帧与上一帧的视图投影矩阵差算出)回答"这个片元在上一帧屏幕的哪里",到历史颜色缓冲的对应位置采样,得到历史颜色 ChistoryC_{history}。

5.3 历史混合

Coutput=α⋅Ccurrent+(1−α)⋅ChistoryC_{output} = \alpha \cdot C_{current} + (1 - \alpha) \cdot C_{history}

混合系数 α\alpha 通常取 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(着色与累积)各管一段——它们针对的本来就是走样的不同来源。