1. 程式人生 > 其它 >如何在Unity URP中利用Sobel新增螢幕空間深度邊緣光

如何在Unity URP中利用Sobel新增螢幕空間深度邊緣光

仔細觀察原神中的人物,可以發現角色周圍有一層白色的內描邊邊緣光:

如何在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邊緣光應該能讓人物看上去更加精緻?

不知道是不是好看了一些,但原神都有這樣的效果那肯定是更加合理的。