天天看點

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

上一篇傳送門:

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

頂點色在卡通渲染中有挺多應用,本篇會在上一篇的基礎上,運用模型頂點色來控制細節。塞爾達荒野之息不一定是用這種方法,也可能是用額外的貼圖來實作,這裡算是抛磚引玉一下,擴充一下思路。(不過這方法效果還挺不錯哦!)

用頂點色控制細節還是有很多好處的,首先就是效果比較平滑(畢竟自動插值),之前曾嘗試過用額外貼圖控制,結果到處都是狗牙鋸齒你懂的,挺難受的。

先上頂點色調整後的最終效果:

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

然後看看之前的效果:

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

之前的效果還是比較粗糙的,有許多細節不是很完美:

①脖子部分被頭遮蓋,應該有陰影

②臉部不希望有太多陰影,應盡量保持明亮

③除了頭發外,其他地方不希望出現高光

④大腿部分的邊緣光不夠明顯,頭發部分的邊緣光又過多了

可知,①、②屬于陰影問題,③屬于高光問題,④屬于邊緣光問題。

有些問題,可以通過拆分模型,給不同的材質球參數來解決,但是這樣會讓材質球個數變多,進而SetPassCall變多,在移動端上可能造成性能問題。是以,頂點色處理無疑是個好辦法,我們可以用頂點色的R、G、B通道分别控制陰影、高光、邊緣光。

接下來将一步步講解如何用頂點色來對細節做修正:

一、準備模型

首先确認模型是否帶有頂點色資訊,在 Unity 内選中模型 Mesh 資源,即可在右下角觀察。

如果是跟我一樣下載下傳的模型的話,很遺憾地會發現隻有 uv 和 skin ,沒有頂點色資訊,這裡得自己用 3ds max 或 maya 重新導出一下(随意指定一下頂點色後導出即可)。

之後,觀察到視窗裡有 colors 就沒問題了。

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)
Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

二、準備工具

可以直接在 3ds Max 或者 Maya 裡繪制頂點色,不過個人還是習慣在 Unity 裡繪制,這樣也友善觀察調整。

(想在 3dsMax 裡刷頂點色的請看下一篇:https://blog.csdn.net/qq_27534999/article/details/101080649)

一般這類工具會有公司的程式專門來開發,如果沒有的話,可以去 Unity 的 Asset Store 搜尋插件來用,這邊推薦一個 TOZ Vertex Painter,小巧且免費。

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

插件導入後,在 Window -> TOZ -> Tools -> Vertex Painter 打開面闆,如下圖所示:

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

此時點選模型時,視窗可能會報錯。會提示模型這邊需要添加 Mesh Collider,且要使用 Mesh Filter 和 Mesh Renderer (這個插件不支援 Skinned Mesh Renderer),這些都要處理一下。模型設定如下圖所示:

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

這時候再選中模型,就可以愉快地刷頂點色了~!刷完後點選 SAVE NEW MESH 會另存為一個新的模型檔案。

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

三、Shader 修改

接下來是 Shader 的修改,擷取頂點色資訊然後對陰影、高光做出調整。

首先,在結構體中加入頂點色,當然别忘了 vert 中要指派:

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

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

...

v2f vert (appdata v)
{
	v2f o;
	...
	o.color = v.color;
	...
	return o;
}
           

然後是針對陰影、高光、邊緣光的計算略有修改,R通道控制陰影,G通道控制高光,B通道控制邊緣光。

R通道:原點乘值區間為[-1,1],這裡我希望控制陰影頂點色的修改能夠覆寫這個區間,因為有些部位我希望永遠是暗部,這樣最極端的情況是把點乘值-1修正為1,這裡用  (頂點色 - 0.5)* 4。

G、B通道:高光、邊緣光兩個通道的點乘值,稍微修正就好,不需要太大的調整,是以這裡用 (頂點色 - 0.5)* 2。

fixed diffValue = dot(worldNormal, worldLightDir)+(i.color.r-0.5)*4;
...
fixed spec = dot(worldNormal, worldHalfDir)+(i.color.g-0.5)*2;
...
fixed rimValue = pow(1 - dot(worldNormal, worldViewDir+(i.color.b-0.5)*2), _RimPower);
           

這樣,Shader的修改就完成了。

四、刷頂點色

剛改完Shader後,視覺表現會很奇怪(見左圖),這是因為頂點色預設黑色(0, 0, 0)。

如果要恢複到原來的狀态,根據我們之前修改 Shader 的公式,隻要将所有頂點色塗上灰色(0.5, 0.5, 0.5)即可(見左圖)。

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

接下來着手解決之前提出的四個問題,根據我們的需求,對頂點色RGB進行調整即可。

①脖子部分被頭遮蓋,應該有陰影 —— 脖子部分 R 值為0

②臉部不希望有太多陰影,應盡量保持明亮 —— 臉部 R 值調高

③除了頭發外,其他地方不希望出現高光 —— 除頭發外其他部位 G 值為0

④大腿部分的邊緣光不夠明顯,頭發部分的邊緣光又過多了 —— 大腿 B值調高,頭發 B值調低

調整後的效果如下圖所示:

Unity Shader 卡通渲染 (三):仿塞爾達荒野之息 Shader(頂點色控制細節)

效果明顯比之前要好多了~!

五、成果

按照慣例,最後給出完整 Shader 如下:

Shader "Custom/ToonShadingSimple_v3"
{
	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;
				fixed4 color : COLOR;
				
			};

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

			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;
				o.color = v.color;
				UNITY_TRANSFER_FOG(o,o.vertex);
				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//return i.color;
				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)+(i.color.g-0.5)*2;
				// 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)+(i.color.r-0.5)*4;
				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)+(i.color.b-0.5)*2, _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
		}
	}
}
           

謝謝觀賞~!

應美術需求,下一篇将講解如何在 3dsMax 裡加載自定義 Shader 刷頂點色

下一篇傳送門:

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

參考資料:

沒有具體參考資料,算是對頂點色應用的一次嘗試吧

繼續閱讀