【URP】Unity[抗鋸齒]原理實現(xian)與對比(bi)
專欄-直達
歷史發展節點
- ?2001年?:MSAA成為DirectX 8標準配置,通過硬件多采樣解決幾何鋸齒
- ?2009年?:NVIDIA推出FXAA,開創后處理抗鋸齒時代
- ?2011年?:SMAA 1.0發布,平衡性能與畫質
- ?2014年?:TAA開始普及,解決動態場景抗鋸齒問題
- ?2017年?:Unity URP集成全系列抗鋸齒方案
抗鋸齒技術實現原理
快速近似抗鋸齒(FXAA)
通過全(quan)屏后處理(li)檢測邊緣像素并(bing)進行顏(yan)色混合,采(cai)用(yong)亮度對(dui)比度閾值(zhi)識別鋸齒區域,使用(yong)低通濾(lv)波器平滑(hua)邊緣。其核心是(shi)犧牲少量銳度換(huan)取性能優勢(shi),處理(li)過程(cheng)完全(quan)在像素空間(jian)進行,不依賴(lai)幾何信息。
?實現原理?:
通過全(quan)屏后處理檢測像(xiang)素間亮度(du)差異(如RGB通道(dao)對比度(du)),對超(chao)過閾值的(de)邊緣(yuan)區(qu)域進行低通濾波混合。例如,當檢測到斜線邊緣(yuan)時,會模糊相鄰像(xiang)素以消(xiao)除階梯狀鋸(ju)齒(chi)?。
核心流程?:
-
亮度計算:使用RGB轉亮度公式
luma = dot(rgb, float3(0.299, 0.587, 0.114))采用(yong)ITU-R BT.709標(biao)準權重(zhong). -
邊緣(yuan)檢測:對比3x3區(qu)域內像素亮(liang)度差(cha),超過閾值(zhi)則(ze)標記為邊緣(yuan)
-
方向(xiang)判定:計算水平/垂直亮度梯(ti)度,確(que)定邊緣走向(xiang)(NW-SE或NE-SW)
-
混合執行:沿邊(bian)緣方向進行5-tap濾(lv)波,加權平均相(xiang)鄰像素顏色
-
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)
分三(san)階段實現:邊(bian)(bian)緣(yuan)(yuan)檢(jian)測(基(ji)于顏色/深度差)、權(quan)重(zhong)計(ji)算(分析(xi)邊(bian)(bian)緣(yuan)(yuan)模式)、混合執行(沿邊(bian)(bian)緣(yuan)(yuan)方向(xiang)(xiang)插值)。相比FXAA能保留更(geng)多高頻細節,通過(guo)形態學處理(li)識別像素級邊(bian)(bian)緣(yuan)(yuan)走向(xiang)(xiang)。
核心原理流程
-
?邊緣檢測階段?:使(shi)用Sobel算子分析像素亮度(du)梯度(du),生成(cheng)邊(bian)(bian)緣(yuan)紋理(分為水平和(he)垂直邊(bian)(bian)緣(yuan))
-
?權重計算階段?:通過AreaTex和SearchTex分(fen)析邊緣形(xing)態(L形(xing)/T形(xing)/對(dui)角線),計算混合權重
-
?混合執行階段?:根據權(quan)重對邊緣像素進(jin)行雙線性插值混合,保(bao)留高頻細節
-
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)
在(zai)光柵化階段對(dui)(dui)每個(ge)像素(su)進(jin)行多重采樣(yang)(2x/4x/8x),計算(suan)覆蓋(gai)率和深度(du)值后合并樣(yang)本。僅對(dui)(dui)幾何邊緣有效,通(tong)過硬件加(jia)速實現物理(li)級抗鋸(ju)齒(chi),但對(dui)(dui)著色鋸(ju)齒(chi)無效且消耗(hao)顯存帶寬。
實現原理?:
在(zai)光柵化(hua)階段對每個像(xiang)素進行多重采樣(yang)(如4x MSAA采樣(yang)4個深度/顏色值),合(he)并時通過(guo)權重計算平滑邊緣。例如,三角形邊緣像(xiang)素會混合(he)部分覆蓋的樣(yang)本?。
核心原理流程
-
?多重采樣階段?:硬件在光(guang)柵化時對每個(ge)像素生(sheng)成多(duo)個(ge)子樣(yang)本(2x/4x/8x),分別計算深度(du)和模板值
-
?樣本合并階段?:通過加(jia)權平(ping)均子樣(yang)本(ben)顏(yan)色值生成最終像素輸(shu)出,平(ping)滑幾何(he)邊緣鋸齒
-
?深度一致性檢測?:自(zi)動處(chu)理(li)子樣本間的深度差異,保留銳利(li)幾何(he)輪(lun)廓
-
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%帶寬)
- 對著色器鋸齒無效(如紋理過濾)?
限制?:
-
需關閉(bi)URP的延遲渲染(ran)功能?
| 特性 | MSAA | FXAA/SMAA |
| --- | --- | --- |
| 處理階段 | 光柵化階段 | 后處理階段 |
| 效果范圍 | 僅幾何邊緣 | 全圖像 |
| 性能消耗 | 中-高(取決于采樣數) | 低-中 |
| 兼容(rong)性(xing) | 需硬件支持 | 全平臺通用 |
時間抗鋸齒(TAA)
利用歷史幀數(shu)據和運(yun)動(dong)向量,將(jiang)當前(qian)幀與前(qian)一幀抗鋸齒結果進行時域(yu)混合。通(tong)過重投影(ying)技術(shu)解決動(dong)態物(wu)體問題,需配合動(dong)態模(mo)糊抑制重影(ying)現(xian)象,對(dui)動(dong)態場景效果最佳。
?實現原理?:
利用歷史幀數據(運動向量+深度緩沖)進行時域(yu)混合:
- ?重投影?:將當前幀與歷史幀對齊
- ?抖動補償?:通過隨機抖動減少重影
- ?累積濾波?:加權融合多幀結果?
核心原理流程
- ?幀間抖動采樣?:通過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項目(mu)需(xu)保持60FPS時(shi),其性(xing)能消(xiao)耗(hao)僅為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計算
專欄-直達
(歡迎點贊留言探討,更(geng)多(duo)人加(jia)入(ru)進來能更(geng)加(jia)完善這個探索的(de)過程,??)

歷史發展節點 ?2001年?:MSAA成為DirectX 8標準配置,通過硬件多采樣解決幾何鋸齒 ?2009年?:NVIDIA推出FXAA,開創后處理抗鋸齒時代 ?2011年?:SMAA 1.0發布,