《抗锯齿全解析:MSAA、FXAA、TAA 与时序超分》

锯齿是光栅化把连续几何离散成像素网格的必然产物。本文从锯齿成因与采样理论讲起,系统对比几何级 MSAA、后处理级 FXAA/MLAA/SMAA、时序级 TAA 三类方案的原理、成本与画质,深入时序累积的历史缓冲、抖动与鬼影抑制,最后延伸到 DLSS/FSR 这类以超分为目标的时域重建技术,给出工程组合与选型建议。

一根斜线画在像素网格上会变成"阶梯",这就是锯齿。锯齿源于采样不足:奈奎斯特定理要求采样率至少两倍于信号最高频率,而屏幕像素永远比几何细节稀疏。抗锯齿(Anti-Aliasing,AA)的思路分成三派:超采样(多采几个样本再平均)、后处理(扫描图像找边缘再模糊)与时序累积(把历史帧当样本池)。本文从成因讲起,覆盖 MSAA/FXAA/TAA 三大方案与 DLSS/FSR 超分原理,并与 后处理管线 衔接。


一、锯齿成因与采样理论

一句话:锯齿是欠采样导致的频谱混叠——只要采样密度跟不上几何/纹理频率,混叠就必然出现,抗锯齿的本质是"补采样"或"压高频"。

1.1 混叠(Aliasing)的数学

理想抗锯齿:把像素覆盖区域内的信号做积分(区域平均)。数学上等价于先低通滤波、再点采样。无滤波的直接采样会产生两类混叠:

  • 几何锯齿(边缘 jaggies):三角形边缘呈阶梯状;
  • 纹理闪烁/摩尔纹(shimmer / moiré):远处纹理高频超过采样率,像素随机抽取纹理样本,出现闪烁与波纹。

1.2 三条对抗路径

流派手段代表成本
空间超采样像素内多样本再平均MSAA / SSAA几何/填充成本高
图像后处理边缘检测 + 平滑FXAA / MLAA / SMAA很低
时域累积多帧采样叠加TAA低,需运动向量
时域超分低分辨率多帧重建DLSS / FSR中等

任何方案的最终目标都是逼近"像素覆盖区域积分",只是采样来源不同:MSAA 在像素内部布点,TAA 在时间轴布点,超分在时间与分辨率两个维度同时布点。

1.3 采样模板

// 像素内多点采样(2x2 旋转网格)示意
vec2 samples[4] = vec2[](
    vec2(-0.25, -0.25), vec2( 0.25, -0.25),
    vec2(-0.25,  0.25), vec2( 0.25,  0.25));
vec4 avg = vec4(0.0);
for (int i = 0; i < 4; ++i) {
    vec2 uv = (gl_FragCoord.xy + samples[i]) * invScreenSize;
    avg += texture(tex, uv);
}
fragColor = avg / 4.0;   // 4x SSAA 的最简实现

二、MSAA:多重采样抗锯齿

一句话:MSAA 只在像素内增加"覆盖率样本",几何边缘处每个样本独立测试三角形覆盖、颜色样本可共享,因此锯齿被消除而着色开销远低于全分辨率超采样。

2.1 原理

像素内放置 N 个采样点(2x、4x、8x)。三角形边缘跨越像素时,N 个样本各自独立测试是否被覆盖:被覆盖的样本执行像素着色(通常整像素共享一次),未被覆盖的样本用邻近像素颜色填充。最后 resolve(解析)时按各样本覆盖率混合。

像素被三角形部分覆盖:
  × × × ·          ×=被覆盖样本
  × × · ·          覆盖率 6/16,颜色 = 三角形色×(6/16) + 背景×(10/16)
  × · · ·
  · · · ·

2.2 关键特性

  • 几何边缘完美:覆盖率在亚像素精度上做,斜边平滑;
  • 着色共享:一个像素的所有覆盖样本共享一次着色(sample shading 关闭时),比 4x SSAA 的 4 次着色便宜得多;
  • 局限:只解决几何锯齿,不解决纹理闪烁(纹理欠采样仍发生,需 mipmap + 各向异性过滤);
  • 成本:frame buffer 内存 N 倍,resolve 有带宽开销;延迟渲染里 G-Buffer 多采样后光照无法逐样本执行,导致 MSAA 失效(见 延迟渲染)。
// C++/Vulkan:开启 4x MSAA
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_4_BIT;
VkImageCreateInfo img = {};
img.samples = sampleCount;                 // 渲染目标采样数
// 渲染后 resolve 到普通图像
vkCmdResolveImage(cmd, msaaImage, MSAA_LAYOUT,
                       resolvedImage, RESOLVED_LAYOUT,
                       1, &region);

三、FXAA:快速近似后处理抗锯齿

一句话:FXAA 在最终图像上扫描边缘、沿梯度方向模糊并混合,一次全屏后处理就完成抗锯齿,代价极低,但代价是模糊细节与"软化"画面。

3.1 原理

FXAA(Fast Approximate Anti-Aliasing)把抗锯齿变成图像处理:

  1. 对每个像素计算局部亮度(luminance),用 3×3 邻域检测是否存在边缘;
  2. 估计边缘方向与梯度,取垂直于边缘的附近像素做混合;
  3. 输出混合后的颜色,使阶梯边缘在视觉上平滑。
边缘检测: 比较像素亮度与上下左右邻居
    |N|
  L |C| R       C 位于边缘 → 沿梯度方向 G 混合两端
    |B|

3.2 优点与缺点

  • 优点:与管线无关,任何引擎最后跑一遍就行;移动端友好;无需运动向量、无需多采样缓冲。
  • 缺点:边缘被"柔和"化,纹理细节与文字也会被模糊;斜 45 度边缘仍有残影;无法处理纹理闪烁。

3.3 FXAA 变体

  • FXAA 3.11(NVIDIA 开源):质量/速度分档(QUALITY 1~5);
  • FXAA 1/2/3:不同模糊半径与步数的简化版;
  • 常作为 TAA 的补充或移动端主力。
// GLSL:FXAA 最简版(单 pass 后处理)
vec3 fxaa(sampler2D tex, vec2 uv, vec2 rcpFrame) {
    float lumaC = luma(textureLod(tex, uv, 0.0).rgb);
    float lumaN = luma(textureLod(tex, uv + vec2(0.0,  rcpFrame.y), 0.0).rgb);
    float lumaS = luma(textureLod(tex, uv + vec2(0.0, -rcpFrame.y), 0.0).rgb);
    float lumaE = luma(textureLod(tex, uv + vec2( rcpFrame.x, 0.0), 0.0).rgb);
    float lumaW = luma(textureLod(tex, uv + vec2(-rcpFrame.x, 0.0), 0.0).rgb);

    float maxL = max(max(lumaN, lumaS), max(lumaE, lumaW));
    float minL = min(min(lumaN, lumaS), min(lumaE, lumaW));
    if (lumaC < minL || lumaC > maxL) {          // 不在邻域范围 → 边缘
        // 沿最大梯度方向采样混合……
    }
    return textureLod(tex, uv, 0.0).rgb;
}

四、MLAA 与 SMAA:形态学抗锯齿

一句话:MLAA 扫描图像识别"U 形/折线"等边缘图案,沿边缘方向做平滑重采样,比 FXAA 更保细节;SMAA 结合边缘检测、图案匹配与时序抖动,是后处理派的画质标杆。

4.1 MLAA(Morphological AA)

MLAA 在图像上寻找特定的边缘形态(斜线、折角),根据图案决定哪些像素属于同一条边缘,然后沿边缘方向重采样、把过渡摊平。相比 FXAA 的"邻域模糊",MLAA 更接近"重建正确边缘"。

4.2 SMAA(Enhanced Subpixel Morphological AA)

SMAA 是 MLAA 的进化:

  • 边缘检测:用深度/法线/颜色多线索找几何边缘,而非仅亮度;
  • 图案匹配:预置数十种边缘形态的查找表(LUT),按图案做形态重建;
  • 亚像素:对小边缘用子像素偏移消除;
  • 时域:可选 SMAAT2x 结合抖动两帧提升质量。
SMAA 三阶段:
  1. 边缘检测(Luma/深度/法线)
  2. 形态学权重计算(查 LUT)
  3. 混合输出 + 可选时域平滑

4.3 对比

特性FXAAMLAASMAA
边缘线索亮度亮度/颜色亮度/深度/法线
重建方式梯度混合形态匹配LUT 图案 + 形态
细节保留差中好
成本极低低中低
移动端首选可用可用

五、TAA:时序抗锯齿

一句话:TAA 把"像素内多采样"换成"多帧多采样"——每帧给像素一个亚像素抖动偏移,再用历史帧累积,用时间换空间,是现代 AAA 引擎的默认抗锯齿。

5.1 原理

TAA(Temporal Anti-Aliasing)流程:

  1. 投影矩阵抖动:每帧把投影矩阵偏移亚像素量(Halton 序列),使每个像素实际采样位置在时间轴上铺开;
  2. 运动向量(Motion Vector):记录当前帧每个像素对应上一帧的位置,用于重投影(reprojection);
  3. 历史累积:把当前帧颜色与上一帧结果按权重混合(通常 0.8~0.95 偏向历史),形成时间上的超采样平均;
  4. 反馈循环:结果写回历史缓冲,下一帧继续累积。
帧 n 像素(x,y) 采样位置 = (x + JitterX(n), y + JitterY(n))
重投影: 用运动向量把历史颜色搬回当前像素 → 混合 → 累积

5.2 数学形式

设当前帧颜色 C_t、历史颜色 C_{t-1}(经重投影),TAA 输出:

C_t^AA = (1 - α) · C_t + α · history(C_{t-1})
α 通常取 0.9 左右 → 历史占比高,时间轴样本多,噪声被平均掉
// GLSL:TAA 累积 Pass
vec4 cur = texture(colorTex, uv);
vec2 mv  = texture(motionTex, uv).xy;          // 运动向量(当前→上一帧)
vec2 histUV = uv + mv;
vec4 hist = texture(historyTex, histUV);

// 历史颜色邻域 clamp(抑制鬼影的核心,见下章)
vec4 boxMin, boxMax;
neighborhoodClamp(cur, hist, boxMin, boxMax);
hist = clamp(hist, boxMin, boxMax);

float blend = mix(0.90, 0.05, length(mv) * 50.0); // 运动越大越信当前帧
fragColor = mix(cur, hist, blend);

5.3 优点与代价

  • 优点:几乎全免费地等效于 8~16x 超采样;同时降低远处纹理闪烁;无需 MSAA 缓冲,延迟渲染友好。
  • 代价:需要额外运动向量缓冲;静止时轻微抖动/模糊;运动区域残留拖影(ghosting);对后处理链顺序敏感。

六、历史缓冲与鬼影抑制

一句话:TAA 的成败全在历史缓冲——错误的颜色被无限累积就是鬼影,所以必须有重投影校正、邻域 clamp 与运动感知混合三道防线。

6.1 鬼影的来源

历史颜色是上一帧的真实颜色,但如果这一帧某处几何/光照变化了(物体移开、遮挡切换、光源移动),重投影出的历史颜色可能来自"已经不该在这里的东西",累积后形成拖影或残像。

6.2 三道防线

  • 运动向量正确性:深度/表面法线参与重投影可减少"贴图滑动"(skidding);
  • 邻域 clamp / clip:在当前帧像素的 3×3 邻域内求颜色 AABB(或凸包),把历史颜色 clamp 进该区间,丢弃"明显不属于这里"的历史;
  • 运动感知混合:运动向量越大,历史权重越低,动态区域及时丢弃旧历史。
// GLSL:邻域 AABB clamp(3x3 采样颜色极值)
vec3 nn = textureLod(colorTex, uv + vec2(-r.x,-r.y), 0.0).rgb;
vec3 pp = textureLod(colorTex, uv + vec2( r.x, r.y), 0.0).rgb;
vec3 pn = textureLod(colorTex, uv + vec2( r.x,-r.y), 0.0).rgb;
vec3 np = textureLod(colorTex, uv + vec2(-r.x, r.y), 0.0).rgb;
vec3  boxMin = min(min(min(nn,pp),pn),np);
vec3  boxMax = max(max(max(nn,pp),pn),np);
hist = clamp(hist, boxMin, boxMax);

6.3 失效场景

  • 不写运动向量(如后处理特效、粒子、水面反射);
  • 半透明层历史未分类(透明物体与实体混合累积);
  • 抖动序列在运动模糊之前,导致模糊与抖动互相干扰。

工程上常把"不参与 TAA 的物体"单独渲染并排除其运动向量,或对运动向量图做膨胀(dilate)以避免边缘漏缝。


七、时域超分:DLSS、FSR 与 TSR

一句话:超分不再只是抗锯齿,而是"用低分辨率多帧重建高分辨率"——先低分辨率渲染省算力,再用时域历史补回细节,本质是 TAA 在分辨率维度的推广。

7.1 从 TAA 到超分

TAA 在"目标分辨率"上做时间累积;DLSS/FSR 则先以更低分辨率(如 1080p)渲染,再用每帧的抖动偏移 + 历史帧重建出 4K 图像。重建质量取决于:

  • 运动向量的精度;
  • 历史有效时长(历史信息能"借用"多少过去帧);
  • 重建网络/算子的上采样能力。

7.2 各家方案

方案厂商手段依赖硬件
DLSS 2/3NVIDIA神经网络超分 + 时序RTX Tensor Core
FSR 2/3AMD算法时空重建 + 抖动无(通用)
XeSSIntel神经网络 + XMX 单元通用/ARC
TSREpic引擎内建时域重建无(UE 内)

DLSS 2 的流程:低分辨率渲染 → 运动向量 → 神经网络把当前帧与历史特征映射到高分辨率 → 与低分辨率输入融合。FSR 2 则用"时域重投影 + 邻域重建"的纯算法路线,不依赖专用硬件。

7.3 超分与画质的辩证

超分不是简单的"放大+锐化":

  • 低分辨率输入损失的高频(细小几何/文字)靠时域恢复,静止画面接近原生;
  • 动态画面时历史失效,重建退化为"空间放大",质量下降;
  • 半透明、粒子、体积光等高频特效是超分的重灾区,需单独处理。
超分质量对比(主观,1080p→4K):
  原生 4K > DLSS 质量档 ≈ 原生 4K(静止) > FSR2 质量档
  DLSS 性能档 ≈ 原生 1080p,但渲染成本只有 1/4

八、选型与工程组合

一句话:没有万能的抗锯齿——桌面 AAA 选"TAA + 超分",追求锐度选 SMAA,移动端选"FXAA 或 SMAA 轻量档",VR 选 Forward+ 的 MSAA;关键是让方案与管线、平台、画质目标对齐。

8.1 组合矩阵

场景推荐组合理由
桌面 AAA(延迟)TAA + 可选 DLSS/FSR延迟管线无 MSAA,TAA 通用
桌面竞技(锐度优先)SMAA 或关 AA追求像素清晰、低延迟
移动端游戏FXAA / SMAA 1x成本与功耗敏感
VR / XRMSAA(Forward+)或 MSAA+FXAA边缘质量直接关系眩晕
影视级预览TAA + 超分 4x时域充分累积

8.2 实施建议

  • 先接 TAA 并把运动向量正确写入;它是后续超分的必要基础设施;
  • TAA 的前置:抖动发生在投影矩阵,必须在运动向量生成之前;
  • TAA 的后置:色调映射之后再做,避免 HDR 高频被压扁;
  • 用 RenderDoc 对比"关 AA / MSAA / TAA / FXAA"的边缘质量与带宽曲线;
  • 超分仅在 GPU 有余量时启用,低端机用 FSR 性能档换取帧率。

8.3 常见陷阱

  • TAA 与运动模糊共用同一抖动序列,产生闪烁;
  • 抖动在裁剪空间做但运动向量在屏幕空间生成,量纲不一致导致历史错位;
  • 历史缓冲用低精度格式(RGBA8)累积多帧后色带/条带化;
  • 超分后接锐化过量,边缘产生光晕(halo)。
// C++:抖动矩阵注入示例(Halton 2x3 序列,投影前累加)
mat4 jitteredProj = proj;
jitteredProj[2][0] += halton2[frame % 8] * 2.0 / screenWidth;
jitteredProj[2][1] += halton3[frame % 8] * 2.0 / screenHeight;
// 该矩阵用于几何 Pass,同时把结果写入 Motion Vector Pass 的对齐逻辑

总结

抗锯齿的本质是与采样定理博弈:MSAA 用像素内空间样本,FXAA/SMAA 用图像后处理重建,TAA 用时间轴累积历史,DLSS/FSR 把时域思想延伸到超分。三条路线各有权衡——MSAA 画质最好但昂贵且与延迟渲染冲突,后处理最便宜但软化细节,TAA 性价比最高但依赖运动向量与鬼影抑制,超分则进一步用算力换分辨率。

方案类型成本画质依赖
MSAA空间超采样高高前向管线
FXAA后处理极低低无
SMAA形态学后处理低中高无
TAA时序累积低高运动向量
DLSS/FSR时域超分中极高Tensor Core / 通用

实践路径:先为管线补全运动向量缓冲,接入 TAA 并调好邻域 clamp 与混合权重;再对比 FXAA/SMAA 处理残留边缘;有超分需求时在 TAA 基础上接 FSR2 或 DLSS,最后用 RenderDoc 逐帧检查历史缓冲与鬼影,找到质量与性能的最优点。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机图形学」更多文章

  1. 《glTF 资产格式与场景加载管线》
  2. 《实时全局光照:GI 探针、SSGI、体积光照与 Lumen 思路》
  3. 《Mesh Shader 新一代几何管线:Task/Mesh 着色器》