如何在Unity URP中利用Sobel新增螢幕空間深度邊緣光
阿新 • • 發佈:2022-05-28
仔細觀察原神中的人物,可以發現角色周圍有一層白色的內描邊邊緣光:
如何在Unity URP中實現類似的效果呢?參考了Adrian Mendez大佬的Youtbue教程,我也嘗試著用新增Render Feature的方式實現該效果。實現大致思路是使用Sobel運算元對相機深度影象進行邊緣檢測:
根據大佬提供的思路寫了一個Shader,大概就是在原圖的基礎上把邊緣光再疊加上去:
Shader "Unlit/SobelRimLight" { Properties { _MainTex("Texture", 2D) = "white" {} _ThicknessX("ThicknessX", Float) = 0.01 _ThicknessY("ThicknessY", Float) = 0.01 _MaxThickness("MaxThickness", Float) = 0.01 _Intensity("Intensity", Range(0,1)) = 0.01 _LerpValue("LerpValue", Range(0,1)) = 1 _Distance("Distance", Float) = 1 _Color("RimLightColor",Color) = (0,0,0) } SubShader { Tags { "RenderType" = "Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 scrPos : TEXCOORD2; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; float _ThicknessX; float _ThicknessY; float _MaxThickness; float _Intensity; float _LerpValue; float _Distance; sampler2D _CameraDepthTexture; float4 _CameraDepthTexture_ST; float4 _Color; static float2 sobelSamplePoints[9] = { float2(-1,1),float2(0,1),float2(1,1), float2(-1,0),float2(0,0),float2(1,0), float2(-1,-1),float2(0,-1),float2(1,-1), }; static float sobelXMatrix[9] = { 1,0,-1, 2,0,-2, 1,0,-1, }; static float sobelYMatrix[9] = { 1,2,1, 0,0,0, -1,-2,-1, }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); o.scrPos = ComputeScreenPos(o.vertex); return o; } fixed4 frag(v2f i) : SV_Target { float2 sobel = 0; //獲得深度 float originDepth = tex2D(_CameraDepthTexture, i.uv); //轉化為線性0-1深度,這樣0.5就表示處於相機到farPlane一半的位置了 originDepth= Linear01Depth(originDepth); //找到該畫素離相機的真實距離,_ProjectionParams.z表示_Camera_FarPlane的距離。https://docs.unity.cn/Packages/[email protected]/manual/Camera-Node.html float depthDistance = _ProjectionParams.z*originDepth; float2 adaptiveThickness = float2(_ThicknessX, _ThicknessY); if (depthDistance <= 0) adaptiveThickness = float2(_MaxThickness, _MaxThickness); else {//根據距離對邊緣光厚度進行線性縮放 adaptiveThickness = adaptiveThickness / depthDistance; } adaptiveThickness = min(adaptiveThickness, float2(_MaxThickness, _MaxThickness)); for (int id = 0; id < 9; id++) { float2 screenPos = i.uv + sobelSamplePoints[id] * adaptiveThickness; float depth = tex2D(_CameraDepthTexture, screenPos); depth = Linear01Depth(depth); sobel += depth * float2(sobelXMatrix[id], sobelYMatrix[id]); } fixed4 previousColor = tex2D(_MainTex, i.uv); fixed4 rimLightColor = _Color * step(_Distance,length(sobel)*_ProjectionParams.z); //疊加到原來的顏色上 fixed4 col = previousColor + lerp(previousColor*rimLightColor, rimLightColor, _LerpValue) * _Intensity; return col; } ENDCG } } }
由於是專門用來後處理的Shader,使用的話先建立一個材質球,可以看到有如下引數(由於是自己隨便寫的,可能引數定義不太符合規範):
ThicknessX和Y分別控制橫向和縱向的邊緣光寬度,MaxThickness控制最大邊緣光寬度;Intensity控制光的強度;LerpValue越大,邊緣光顏色就越像設定的RimLightColor的顏色;Distance控制邊緣的深度閾值。
這裡還是像之前一樣使用新增Blit Renderer Feature的方式,先把相關程式碼從Github弄下來,然後可以在UniversalRenderPipelineAsset_Renderer上點選Add Renderer Feature新增一個Blit Feature,再把材質拖進去就行了:
效果對比,用Sobel邊緣光應該能讓人物看上去更加精緻?
不知道是不是好看了一些,但原神都有這樣的效果那肯定是更加合理的。