Unity Shader 卡通渲染 (二):仿塞爾達荒野之息 Shader(高光、平滑)

上一篇傳送門:

https://blog.csdn.net/qq_27534999/article/details/100661120

本篇會在上一篇的基礎上,添加高光與平滑效果。

一、高光

塞爾達中的頭髮高光應該是用普通高光加上額外貼圖控制的,這邊先做普通的高光。

還是用老辦法,視角方向和光照方向相加後歸一化,之後與法線點乘:

fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); //高光計算用
...
fixed spec = dot(worldNormal, worldHalfDir);
fixed4 specular = _Specular * lerp(0,1,step(0, spec+_SpecularScale-1)) * step(0.001, _SpecularScale);
...
fixed4 rim = light * rimStep * 0.5 * diffStep * _RimColor;

效果如下,臉部其實不應有高光的,但模型是一體的,沒法分別設置參數。

建議把需要高光的模型單獨拆出來,設置合適的 SpecularScale 值。(或者用下一篇講的頂點色控制)

二、平滑

細心的你肯定會發現鋸齒感嚴重,此時需要做一些平滑處理。(雖然塞爾達荒野之息貌似對鋸齒不作處理……)

方法也很簡單,就是使用 smoothstep 函數。

smoothstep函數具體細節可以看這裏:https://blog.csdn.net/u010333737/article/details/82859246

fixed w = fwidth(spec)*2.0;

模仿 Unity Shader 入門精要,計算一個 w值,然後將代碼中部分 step 改爲 smoothstep,用上 w值即可(也可用一個較小的值替代 w值,實際測試效果差別不大)。

fixed4 specular = _Specular * lerp(0,1,smoothstep(-w, w, spec+_SpecularScale-1)) * step(0.001, _SpecularScale);
...
fixed diffStep = smoothstep(-w+_ShadowThreshold, w+_ShadowThreshold, diffValue);
...
fixed rimStep = smoothstep(-w+_RimThreshold, w+_RimThreshold, rimValue);

將之前的 step 改爲 smoothstep後,邊緣會更加平滑、柔和:

三、成果

至此,完整 Shader 如下:

Shader "Custom/ToonShadingSimple_v2"
{
	Properties
	{
		[Header(Main)]
		_MainTex ("Texture", 2D) = "white" {}
		_Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0)
		_RimColor ("RimColor", Color) = (1.0, 1.0, 1.0, 1.0)
		_ShadowThreshold ("ShadowThreshold", Range(-1.0, 1.0)) = 0.2
		_ShadowBrightness ("ShadowBrightness", Range(0.0, 1.0)) = 0.6
		_RimThreshold ("RimThreshold", Range(0.0, 1.0)) = 0.35
		_RimPower ("RimPower", Range(0.0, 16)) = 4.0
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_SpecularScale("Specular Scale", Range(0, 0.1)) = 0.02
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
			Cull Back
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fog
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
				
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;
				float3 worldPos : TEXCOORD2;
				UNITY_FOG_COORDS(3)
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Color;
			fixed4 _RimColor;
			fixed _ShadowThreshold;
			fixed _ShadowBrightness;
			fixed _RimThreshold;
			half _RimPower;
			fixed4 _Specular;
			fixed _SpecularScale;
			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				UNITY_TRANSFER_FOG(o,o.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				fixed3 worldNormal = normalize(i.worldNormal); //法線 N
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); //光照方向 L
				fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); //視角方向 V
				fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir); //高光計算用

				// sample the texture
				fixed4 col = tex2D(_MainTex, i.uv); 
				fixed spec = dot(worldNormal, worldHalfDir);
				// w值也可用一個較小的值代替,效果差別不大
				fixed w = fwidth(spec)*2.0;
				fixed4 specular = _Specular * lerp(0,1,smoothstep(-w, w, spec+_SpecularScale-1)) * step(0.001, _SpecularScale);
				fixed diffValue = dot(worldNormal, worldLightDir);
				fixed diffStep = smoothstep(-w+_ShadowThreshold, w+_ShadowThreshold, diffValue);
				fixed4 light = _LightColor0 * 0.5 + 0.5;
				fixed4 diffuse = light * col * (diffStep + (1 - diffStep) * _ShadowBrightness) * _Color;

				// 模仿參考文章的方法,感覺效果不是太好
				// fixed rimValue = 1 - dot(worldNormal, worldViewDir);
				// fixed rimStep = step(_RimThreshold, rimValue * pow(dot(worldNormal,worldLightDir), _RimPower));
				
				fixed rimValue = pow(1 - dot(worldNormal, worldViewDir), _RimPower);
				fixed rimStep = smoothstep(-w+_RimThreshold, w+_RimThreshold, rimValue);
				
				fixed4 rim = light * rimStep * 0.5 * diffStep * _RimColor;
				fixed4 final = diffuse + rim + specular;

				// apply fog
				UNITY_APPLY_FOG(i.fogCoord, final);
				
				return  final;
			}
			ENDCG
		}
	}
}

謝謝觀賞!~

下一篇傳送門:

https://blog.csdn.net/qq_27534999/article/details/100985558

參考資料:

1、https://roystan.net/articles/toon-shader.html

2、《Unity Shader 入門精要》

3、https://blog.csdn.net/u010333737/article/details/82859246

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章