历史发展节点
2001年:MSAA成为DirectX 8标准配置,通过硬件多采样解决几何锯齿
2009年:NVIDIA推出FXAA,开创后处理抗锯齿时代
2011年:SMAA 1.0发布,平衡性能与画质
2014年:TAA开始普及,解决动态场景抗锯齿问题
2017年:Unity URP集成全系列抗锯齿方案
抗锯齿技术实现原理
快速近似抗锯齿(FXAA)
通过全屏后处理检测边缘像素并进行颜色混合,采用亮度对比度阈值识别锯齿区域,使用低通滤波器平滑边缘。其核心是牺牲少量锐度换取性能优势,处理过程完全在像素空间进行,不依赖几何信息。
实现原理:
通过全屏后处理检测像素间亮度差异(如RGB通道对比度),对超过阈值的边缘区域进行低通滤波混合。例如,当检测到斜线边缘时,会模糊相邻像素以消除阶梯状锯齿。
核心流程:
亮度计算:使用RGB转亮度公式luma = dot(rgb, float3(0.299, 0.587, 0.114)) 采用ITU-R BT.709标准权重.
边缘检测:对比3x3区域内像素亮度差,超过阈值则标记为边缘
方向判定:计算水平/垂直亮度梯度,确定边缘走向(NW-SE或NE-SW)
混合执行:沿边缘方向进行5-tap滤波,加权平均相邻像素颜色
FXAA.shader
关键参数说明
亮度计算:采用0.2126729, 0.7151522, 0.0721750权重符合sRGB标准
边缘阈值:edgeThresholdMin防止过度处理平滑区域,edgeThreshold动态适应高亮度区域
方向判定:通过水平和垂直方向的二阶差分确定主边缘方向
子像素混合:subpixelBlend控制亚像素级混合强度,改善细线表现
URP集成要点
通过RenderFeature添加到URP渲染管线
需在相机设置中禁用MSAA/TAA等冲突抗锯齿
纹理采样使用URP标准的SAMPLE_TEXTURE2D宏
Shader "Hidden/Universal Render Pipeline/FXAA"
{
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float4 _MainTex_TexelSize;
// ITU-R BT.709亮度系数
float Luminance(float3 rgb)
{
return dot(rgb, float3(0.2126729, 0.7151522, 0.0721750));
}
// 边缘检测结构体
struct EdgeData {
float m, n, e, s, w;
float highest, lowest, contrast;
};
EdgeData SampleLumaNeighborhood(float2 uv)
{
EdgeData ed;
float2 offset = _MainTex_TexelSize.xy;
ed.m = Luminance(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv).rgb);
ed.n = Luminance(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv + float2(0, offset.y)).rgb);
ed.e = Luminance(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv + float2(offset.x, 0)).rgb);
ed.s = Luminance(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv - float2(0, offset.y)).rgb);
ed.w = Luminance(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv - float2(offset.x, 0)).rgb);
ed.highest = max(max(max(max(ed.n, ed.e), ed.s), ed.w), ed.m);
ed.lowest = min(min(min(min(ed.n, ed.e), ed.s), ed.w), ed.m);
ed.contrast = ed.highest - ed.lowest;
return ed;
}
float4 FXAA_Pass(float2 uv)
{
// 参数配置
float edgeThresholdMin = 0.03125;
float edgeThreshold = 0.125;
float subpixelBlend = 0.75;
EdgeData ed = SampleLumaNeighborhood(uv);
// 边缘检测条件
if(ed.contrast < max(edgeThresholdMin, ed.highest * edgeThreshold))
return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
// 计算混合方向
float horizontal = abs(ed.n + ed.s - 2.0 * ed.m) * 2.0 +
abs(ed.e + ed.w - 2.0 * ed.m);
float vertical = abs(ed.e + ed.w - 2.0 * ed.m) * 2.0 +
abs(ed.n + ed.s - 2.0 * ed.m);
bool isHorizontal = horizontal >= vertical;
// 边缘端点检测
float2 edgeDir = isHorizontal ?
float2(0, _MainTex_TexelSize.y) :
float2(_MainTex_TexelSize.x, 0);
// 5-tap混合
float3 rgbA = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv - edgeDir * 0.5).rgb;
float3 rgbB = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv + edgeDir * 0.5).rgb;
float3 rgbC = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv - edgeDir).rgb;
float3 rgbD = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv + edgeDir).rgb;
// 加权混合
float blendFactor = 0.5 * (Luminance(rgbA) + Luminance(rgbB)) - Luminance(ed.m);
blendFactor = saturate(blendFactor / ed.contrast) * subpixelBlend;
float3 finalColor = lerp(
lerp(rgbC, rgbD, 0.5),
lerp(rgbA, rgbB, 0.5),
blendFactor
);
return float4(finalColor, 1.0);
}
ENDHLSL
SubShader
{
Pass
{
Name "FXAA"
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
struct Attributes {
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings {
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
Varyings Vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
float4 Frag(Varyings input) : SV_Target
{
return FXAA_Pass(input.uv);
}
ENDHLSL
}
}
}
优势:
性能消耗最低(仅需1次全屏采样)
兼容所有GPU架构
劣势:
导致画面整体模糊(尤其影响高光区域)
无法处理时间性锯齿(如动态物体)
限制:
不适用于HDRP的延迟渲染管线
子像素形态抗锯齿(SMAA)
分三阶段实现:边缘检测(基于颜色/深度差)、权重计算(分析边缘模式)、混合执行(沿边缘方向插值)。相比FXAA能保留更多高频细节,通过形态学处理识别像素级边缘走向。
核心原理流程
边缘检测阶段:使用Sobel算子分析像素亮度梯度,生成边缘纹理(分为水平和垂直边缘)
权重计算阶段:通过AreaTex和SearchTex分析边缘形态(L形/T形/对角线),计算混合权重
混合执行阶段:根据权重对边缘像素进行双线性插值混合,保留高频细节
SMAA.hlsl
关键实现解析
三阶段架构:需创建三个独立Pass分别对应边缘检测、权重计算和混合阶段
纹理资源:依赖预计算的AreaTex(存储混合模式)和SearchTex(存储搜索方向),需导入为Texture2D资源
动态阈值:采用相对亮度差(0.1阈值)检测边缘,避免固定阈值导致的过检测
URP集成要点
RenderPass配置:在URP Renderer中按顺序添加三个RenderFeature
纹理绑定:通过_AreaTex和_SearchTex参数传递预计算纹理
性能优化:使用linear_clamp_sampler减少纹理采样开销
// 边缘检测阶段
Texture2D _MainTex;
Texture2D _BlendTex;
SamplerState linear_clamp_sampler;
// 预计算纹理
Texture2D _AreaTex; // 存储混合模式(512x512)
Texture2D _SearchTex; // 存储搜索方向(64x16)
struct EdgeData {
float2 uv;
float4 offsets[3];
};
EdgeData SMAAEdgeDetectionVS(float4 position : POSITION, float2 uv : TEXCOORD0) {
EdgeData output;
output.uv = uv;
float4 texelSize = _MainTex_TexelSize.xyxy * float4(1.0, 1.0, -1.0, -1.0);
output.offsets[0] = uv.xyxy + texelSize.xyxy * float4(-1.0, 0.0, 0.0, -1.0);
output.offsets[1] = uv.xyxy + texelSize.xyxy * float4(1.0, 0.0, 0.0, 1.0);
output.offsets[2] = uv.xyxy + texelSize.xyxy * float4(-2.0, 0.0, 0.0, -2.0);
return output;
}
float4 SMAAColorEdgeDetectionPS(EdgeData input) : SV_Target {
float L = Luminance(_MainTex.Sample(linear_clamp_sampler, input.uv).rgb);
float delta1 = Luminance(_MainTex.Sample(linear_clamp_sampler, input.offsets[0].xy).rgb) - L;
float delta2 = L - Luminance(_MainTex.Sample(linear_clamp_sampler, input.offsets[0].zw).rgb);
float2 edges = step(float2(0.1, 0.1), abs(float2(delta1, delta2)));
return float4(edges, 0.0, 1.0);
}
// 权重计算阶段
float4 SMAABlendingWeightCalculationPS(EdgeData input) : SV_Target {
float2 area = _AreaTex.Sample(linear_clamp_sampler, input.uv).rg;
float2 search = _SearchTex.Sample(linear_clamp_sampler, input.uv).rg;
float4 weights = float4(area.r, area.g, search.r, search.g);
return weights;
}
// 混合阶段
float4 SMAANeighborhoodBlendingPS(EdgeData input) : SV_Target {
float4 weights = _BlendTex.Sample(linear_clamp_sampler, input.uv);
float3 color = _MainTex.Sample(linear_clamp_sampler, input.uv).rgb;
float3 color1 = _MainTex.Sample(linear_clamp_sampler, input.uv + float2(weights.r, 0.0)).rgb;
float3 color2 = _MainTex.Sample(linear_clamp_sampler, input.uv + float2(0.0, weights.g)).rgb;
return float4(lerp(color, (color1 + color2) * 0.5, weights.b), 1.0);
}
实现原理:
分三阶段处理:
边缘检测:基于颜色/深度梯度识别锯齿边缘
模式分析:通过形态学算法(如腐蚀/膨胀)确定边缘走向
像素混合:沿检测到的边缘方向插值(如斜线边缘按45°方向混合)
优势:
保留更多高频细节(如UI文字锐度)
性能消耗仅为MSAA的1/3
劣势:
对复杂光照锯齿(如SSR反射)效果有限
限制:
需URP 12.0+版本支持
多重采样抗锯齿(MSAA)
在光栅化阶段对每个像素进行多重采样(2x/4x/8x),计算覆盖率和深度值后合并样本。仅对几何边缘有效,通过硬件加速实现物理级抗锯齿,但对着色锯齿无效且消耗显存带宽。
实现原理:
在光栅化阶段对每个像素进行多重采样(如4x MSAA采样4个深度/颜色值),合并时通过权重计算平滑边缘。例如,三角形边缘像素会混合部分覆盖的样本。
核心原理流程
多重采样阶段:硬件在光栅化时对每个像素生成多个子样本(2x/4x/8x),分别计算深度和模板值
样本合并阶段:通过加权平均子样本颜色值生成最终像素输出,平滑几何边缘锯齿
深度一致性检测:自动处理子样本间的深度差异,保留锐利几何轮廓
MSAA_URP.shader
实现解析
硬件级集成:MSAA通过#pragma multi_compile指令激活GPU硬件支持,无需手动实现采样逻辑
深度处理优化:自动处理子样本间的深度差异,保留几何边缘锐度
光照兼容性:演示与URP光照系统的无缝集成,阴影计算同样受益于MSAA
URP配置要点
质量设置:在URP Asset中启用MSAA(2x/4x/8x)
渲染目标:需使用支持MSAA的RenderTexture格式(如RenderTextureFormat.DefaultHDR)
性能考量:4x MSAA在移动端TBR架构上性能损耗较低,适合高端移动设备
Shader "Universal Render Pipeline/MSAA"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass
{
Name "MSAA_Pass"
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile _ _SHADOWS_SOFT
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
Varyings vert(Attributes input)
{
Varyings output;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = vertexInput.positionCS;
output.uv = input.uv;
output.positionWS = vertexInput.positionWS;
return output;
}
half4 frag(Varyings input) : SV_Target
{
// 硬件自动处理MSAA采样
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
// 光照计算(演示MSAA与光照的兼容性)
Light mainLight = GetMainLight();
float3 N = normalize(cross(ddy(input.positionWS), ddx(input.positionWS)));
float diffuse = saturate(dot(N, mainLight.direction));
return color * (diffuse * mainLight.color + mainLight.shadowAttenuation);
}
ENDHLSL
}
}
}
优势:
物理级抗锯齿(对几何边缘效果最佳)
支持硬件加速(如DX12的MSAA优化)
劣势:
显存带宽消耗高(8x MSAA增加50%带宽)
对着色器锯齿无效(如纹理过滤)
限制:
需关闭URP的延迟渲染功能
| 特性 | MSAA | FXAA/SMAA |
| --- | --- | --- |
| 处理阶段 | 光栅化阶段 | 后处理阶段 |
| 效果范围 | 仅几何边缘 | 全图像 |
| 性能消耗 | 中-高(取决于采样数) | 低-中 |
| 兼容性 | 需硬件支持 | 全平台通用 |
时间抗锯齿(TAA)
利用历史帧数据和运动向量,将当前帧与前一帧抗锯齿结果进行时域混合。通过重投影技术解决动态物体问题,需配合动态模糊抑制重影现象,对动态场景效果最佳。
实现原理:
利用历史帧数据(运动向量+深度缓冲)进行时域混合:
重投影:将当前帧与历史帧对齐
抖动补偿:通过随机抖动减少重影
累积滤波:加权融合多帧结果
核心原理流程
帧间抖动采样:通过Halton序列对投影矩阵施加微小偏移,使采样点在时间维度上均匀分布
运动向量追踪:利用_CameraMotionVectorsTexture记录像素位移,结合深度纹理处理边缘运动
历史帧混合:通过线性插值(lerp)将当前帧与历史缓冲数据融合,动态调整混合权重
TAA.shader
关键技术解析
运动向量处理:通过_CameraMotionVectorsTexture获取像素位移,确保历史帧采样位置准确
动态混合策略:基于运动向量长度调整混合权重,静态区域权重低(保留更多历史数据),动态区域权重高(减少拖影)
投影矩阵抖动:在C#脚本中修改相机投影矩阵实现Halton序列偏移,需配合UNITY_MATRIX_PREV_VP矩阵使用
URP集成要点
RenderFeature配置:需创建TAARenderFeature并设置执行时机为RenderPassEvent.BeforeRenderingPostProcessing
双缓冲历史纹理:使用两个RenderTexture交替存储历史帧数据,避免读写冲突
运动向量生成:需为动态物体添加MotionVector Pass,静态物体可直接使用相机运动矩阵
性能优化建议
分辨率降采样:对历史缓冲使用半分辨率纹理(需配合双线性滤波)
边缘锐化后处理:在TAA后添加FXAA或自定义锐化Pass补偿过度模糊
移动端适配:将运动向量计算移至顶点着色器,减少Fragment计算量
该方案相比SMAA能有效减少次像素闪烁,特别适合处理动态植被和细小网格的锯齿问题。实际部署时需注意处理透明物体的运动向量生成问题
Shader "Universal Render Pipeline/TAA"
{
Properties
{
_MainTex("Base (RGB)", 2D) = "white" {}
_HistoryTex("History Buffer", 2D) = "black" {}
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D(_MainTex);
TEXTURE2D(_HistoryTex);
TEXTURE2D(_CameraMotionVectorsTexture);
SAMPLER(sampler_linear_clamp);
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
// Halton序列生成抖动偏移
float2 GetJitterOffset(uint frameIndex)
{
const float2 seq = float2(
0.5f * (frameIndex % 8 + 1) / 8.0f,
0.5f * (frameIndex % 16 + 1) / 16.0f
);
return (seq - 0.5f) * _ScreenParams.zw;
}
Varyings Vert(uint vertexID : SV_VertexID)
{
Varyings output;
output.positionCS = GetFullScreenTriangleVertexPosition(vertexID);
output.uv = GetFullScreenTriangleTexCoord(vertexID);
return output;
}
float4 Frag(Varyings input) : SV_Target
{
// 获取运动向量
float2 motion = SAMPLE_TEXTURE2D(_CameraMotionVectorsTexture, sampler_linear_clamp, input.uv).xy;
// 采样当前帧和历史帧
float3 current = SAMPLE_TEXTURE2D(_MainTex, sampler_linear_clamp, input.uv).rgb;
float3 history = SAMPLE_TEXTURE2D(_HistoryTex, sampler_linear_clamp, input.uv - motion).rgb;
// 动态混合权重(基于运动向量长度)
float blendFactor = saturate(length(motion) * 10.0f);
return float4(lerp(history, current, blendFactor), 1.0);
}
ENDHLSL
SubShader
{
Pass
{
Name "TAA_Pass"
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
}
}
优势:
动态场景抗锯齿效果最佳(如快速移动的物体)
支持复杂光照(如HDRP的全局光照)
劣势:
极端情况下出现重影(如快速切换场景)
限制:
需启用运动向量(Motion Vectors)
不兼容动态分辨率
URP中的选择策略
性能优先场景
选择FXAA:移动端或VR项目需保持60FPS时,其性能消耗仅为SMAA的60%。
画质优先场景
静态场景:MSAA 4x/8x(需关闭延迟渲染)
动态场景:TAA(需启用运动向量)
风格化渲染:SMAA(保留清晰边缘)
特殊配置建议
WebGL项目:避免MSAA(内存限制),推荐SMAA
VR项目:FXAA+TAA组合减少动态模糊
HDRP管线:优先TAA解决复杂光照锯齿
技术决策矩阵:
指标 FXAA SMAA MSAA TAA
几何边缘 中 良 优 优
着色锯齿 差 中 无效 良
动态场景 中 中 优 优
GPU消耗 1x 1.5x 3-8x 2x
显存占用 低 低 高 中