【URP】Unity[抗锯齿]原理实现与对比
【从UnityURP开始探索游戏渲染】专栏-直达
历史发展节点
- 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
 
- 硬件级集成: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,静态物体可直接使用相机运动矩阵
 
- RenderFeature配置:需创建TAARenderFeature并设置执行时机为
- 
性能优化建议 - 分辨率降采样:对历史缓冲使用半分辨率纹理(需配合双线性滤波)
- 边缘锐化后处理:在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 | 
| 显存占用 | 低 | 低 | 高 | 中 | 
注:消耗基准以FXAA为1x计算
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)
 
                    
                     
                    
                 
                    
                
![【URP】Unity[抗锯齿]原理实现与对比](https://img2024.cnblogs.com/blog/3685400/202510/3685400-20251030082614211-1881306615.png) 历史发展节点 2001年:MSAA成为DirectX 8标准配置,通过硬件多采样解决几何锯齿 2009年:NVIDIA推出FXAA,开创后处理抗锯齿时代 2011年:SMAA 1.0发布,
        历史发展节点 2001年:MSAA成为DirectX 8标准配置,通过硬件多采样解决几何锯齿 2009年:NVIDIA推出FXAA,开创后处理抗锯齿时代 2011年:SMAA 1.0发布,
     
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号