首先看下我們的效果需求,我們是一款SLG遊戲,主城中的建築都是3D的模型,玩家到後期主城內會有上百個建築,而且建築都是可以讓玩家隨意擺放的。我們的建築要實現自身的陰影和在地面上的投影。如下圖:
一般我們在遊戲中要實現建築的陰影有兩種方案:1.燈光的實時陰影2.使用Lightmap烘焙陰影信息。
方案1在移動端上使用的很少,如果沒有實時光影的變化,類似天氣系統之類的需求,基本上是不會使用的。要多一個pass去計算陰影,相當於場景中的三角面數翻倍。
方案2是目前大多數移動端上使用的,開銷的話只是用第二套UV去採樣一次Lightmap貼圖而已。但是這種方法烘焙完Lightmap之後就不能修改了,而我們的建築是可以讓玩家隨意擺放的,所以這個方案也並不適用於我們。
我們實現的方案也是和Lightmap類似,都是用模型的第二套UV去採樣一張光影貼圖。只是我們不在Unity裡面去做Lightmap烘焙,而是在三維模型製作的時候就把陰影烘焙在貼圖的Alpha通道裡面。
整個陰影效果分兩部分,自身陰影和地面的投影。自身陰影的實現可以直接畫在Color貼圖上,也就是在製作貼圖的時候就把光影關係畫出來。但是這樣要做到統一光影強弱和後期修改起來都比較麻煩,而且我們為了節省資源很多建築的Color貼圖都是共用的。比如說A建築和B建築的牆用的都是同樣的一塊貼圖,但是A建築可能是在暗面,而B建築可能是在亮面,所以這個就沒有辦法直接把陰影畫在Color貼圖上面了。第二部分是地面的投影,有些遊戲建築下面會加一個地塊,建築的投影就是直接投射在自帶的地塊上,這樣的話可以直接當做自陰影來一起處理,我們的建築是沒有那個自帶地塊的,所以要在單獨建一個面片的模型來接收地面的投影。下面我們來看具體的實現方法。
自身陰影的實現:
我以前是做影視的所以更習慣使用Maya,當然在Max裡面也是一樣的方法。模型導入之後會有一套之前製作貼圖時候的UV,這套UV是用來採樣Color的,我們不能修改。
給這個模型新建一套UV,然後用自動映射將模型每個面的UV都展開,不能有重疊。
接下來我們就打好燈光來烘焙陰影,這裡我們只單獨烘焙陰影所以材質球上不需要貼上Color貼圖,直接白模+主光源+AO渲染就好了。離線渲染器也可以選擇自己喜歡用的習慣的,我這裡用的是Redshift,剛好之前體驗過一下這款GPU離線渲染器,效果和效率都還不錯。
然後在將這個陰影效果烘焙到模型的第二套UV上,千萬不要烘焙到第一套UV上了。
這個操作是可以批量執行的,當所有的建築模型都做完了可以全部擺在場景裡面打好統一的燈光一鍵批量烘焙。烘焙後的貼圖如下:
在用同樣的方法把地面投影也烘焙到接收的面片上,接收的面片就不需要第二套UV了,直接烘焙在第一套默認的UV是就行了。
我們在吧貼圖導入Unity之前,先在PS裡面整合一下貼圖,把模型的烘焙的陰影貼圖放在Color貼圖的Alpha通道裡面。
地面投射的這張陰影貼圖我們其實要的是黑色的投影部分,所以我們在PS裡面把這張圖反轉一下,用黑色的部分來作為Alpha通道,這樣還可以控制強弱和顏色。
把模型和底下接收陰影的面片一起導出FBX,接下來我們開始在Unity裡面寫shader來使用這個陰影貼圖。
Shader部分的處理很簡單,用兩套UV採樣下貼圖然後做乘法計算就行了。
Shader "Custom/Building/Building_SelfShadow" { Properties { _Color("MainColor", Color) = (1,1,1,1) //RGB存儲的是模型的顏色貼圖,Alpha貼圖儲存的是自身的陰影 _MainTexture("MainTexture", 2D) = "white" {} //用一個參數來控制陰影的強弱 _SelfShadow("SelfShadow",Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } Cull Back LOD 100
Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc"
struct appdata { float4 vertex : POSITION; //獲取模型的第一套UV float2 texcoord : TEXCOORD0; //獲取模型的第二套UV float2 texcoord1 : TEXCOORD1; };
struct v2f { float4 pos : SV_POSITION; //用來接收傳遞模型的兩套UV float4 uv : TEXCOORD0; };
sampler2D _MainTexture; fixed _SelfShadow; fixed4 _Color;
v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); //xy的分量存儲模型的第一套UV o.uv.xy = v.texcoord; //zw的分量存儲模型的第二套UV o.uv.zw = v.texcoord1;
return o; }
fixed4 frag (v2f i) : SV_Target { //用第一套UV採樣貼圖的RGB得到Color值 fixed3 texCol = tex2D(_MainTexture, i.uv.xy).rgb; //用第二套UV採樣貼圖的Alpha得到陰影值 fixed shadow = tex2D(_MainTexture, i.uv.zw).a; //Color和Shadow相乘,用SelfShadow參數來控制陰影的強度,值為0時沒有陰影 fixed3 outputColor = texCol * (shadow * _SelfShadow + (1 - _SelfShadow)) * _Color.rgb; return fixed4(outputColor, 1); } ENDCG } } Fallback "Mobile/Unlit" }
地面投影的Shader
Shader "Custom/Building/Building_Shadow" { Properties { //Color的RGB值控制投影的顏色,Alpha值控制投影的強弱 _Color("MainColor", Color) = (1,1,1,1) _MainTex("MainTexture", 2D) = "white" {} } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } Cull Back LOD 100
Pass { Tags { "LightMode" = "ForwardBase" }
ZWrite Off Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; };
struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; };
sampler2D _MainTex; fixed4 _Color;
v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.texcoord;
fixed4 frag (v2f i) : SV_Target { fixed4 col = _Color; col.a *= tex2D(_MainTex, i.uv).r; return col; } ENDCG } } }
這樣就是建築的陰影實現方案,下面一篇文章會介紹下在項目中實際運用的一些優化和流程工具的開發。