一根斜线画在像素网格上会变成"阶梯",这就是锯齿。锯齿源于采样不足:奈奎斯特定理要求采样率至少两倍于信号最高频率,而屏幕像素永远比几何细节稀疏。抗锯齿(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, ®ion);
三、FXAA:快速近似后处理抗锯齿
一句话:FXAA 在最终图像上扫描边缘、沿梯度方向模糊并混合,一次全屏后处理就完成抗锯齿,代价极低,但代价是模糊细节与"软化"画面。
3.1 原理
FXAA(Fast Approximate Anti-Aliasing)把抗锯齿变成图像处理:
- 对每个像素计算局部亮度(luminance),用 3×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 对比
| 特性 | FXAA | MLAA | SMAA |
|---|---|---|---|
| 边缘线索 | 亮度 | 亮度/颜色 | 亮度/深度/法线 |
| 重建方式 | 梯度混合 | 形态匹配 | LUT 图案 + 形态 |
| 细节保留 | 差 | 中 | 好 |
| 成本 | 极低 | 低 | 中低 |
| 移动端 | 首选 | 可用 | 可用 |
五、TAA:时序抗锯齿
一句话:TAA 把"像素内多采样"换成"多帧多采样"——每帧给像素一个亚像素抖动偏移,再用历史帧累积,用时间换空间,是现代 AAA 引擎的默认抗锯齿。
5.1 原理
TAA(Temporal Anti-Aliasing)流程:
- 投影矩阵抖动:每帧把投影矩阵偏移亚像素量(Halton 序列),使每个像素实际采样位置在时间轴上铺开;
- 运动向量(Motion Vector):记录当前帧每个像素对应上一帧的位置,用于重投影(reprojection);
- 历史累积:把当前帧颜色与上一帧结果按权重混合(通常 0.8~0.95 偏向历史),形成时间上的超采样平均;
- 反馈循环:结果写回历史缓冲,下一帧继续累积。
帧 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/3 | NVIDIA | 神经网络超分 + 时序 | RTX Tensor Core |
| FSR 2/3 | AMD | 算法时空重建 + 抖动 | 无(通用) |
| XeSS | Intel | 神经网络 + XMX 单元 | 通用/ARC |
| TSR | Epic | 引擎内建时域重建 | 无(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 / XR | MSAA(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 逐帧检查历史缓冲与鬼影,找到质量与性能的最优点。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。