Unity陰影采用的是 shadow map 的技術,即把攝像機放到光源位置上,看不到的地方就有陰影。
前向渲染中,若一光源開啟了陰影,Unity會計算它的陰影映射紋理(shadow map),它其實是一張深度圖,記錄了從光源位置出發,能看到的場景中距離它最近的表面位置的深度信息。
Unity中采用LightMode為ShadowCaster的Pass來生成這張 shadow map,首先會從本shader中找這個pass,若沒有就從fallback的shader中層層尋找。
然后在fragment shader中,比較該點與shadow map中對應的點的深度,若大於,則處在陰影區域,否則不在陰影區域。
陰影包括投射陰影和接收陰影2個部分。
投射陰影,則需將該物體加入到光源的shadow map的計算中。
接收陰影,則需要對shadow map進行采樣,把采樣結果和最后的光照結果相乘來產生陰影效果。
shader如下:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "Custom/Forward Rendering" { Properties { _MainTex("Main Texture", 2D) = "white" {} _Specular("Specular", Color) = (1,1,1,1) _Gloss("Gloss", Range(8,256)) = 8 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCg.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" sampler2D _MainTex; fixed4 _Specular; float _Gloss; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float4 worldPos: TEXCOORD2; SHADOW_COORDS(3) }; v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex); TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i) : SV_TARGET { fixed4 albedo = tex2D(_MainTex, i.uv); fixed4 ambient = albedo * UNITY_LIGHTMODEL_AMBIENT; float3 worldLight = normalize(UnityWorldSpaceLightDir(i.worldPos.xyz)); float3 worldView = normalize(UnityWorldSpaceViewDir(i.worldPos.xyz)); fixed4 diff = albedo * _LightColor0 * max(0, dot(i.worldNormal, worldLight)); float3 halfDir = normalize(worldView + worldLight); fixed4 spec = albedo * _Specular * pow(max(0, dot(halfDir, i.worldNormal)), _Gloss); float shadow = SHADOW_ATTENUATION(i); fixed4 col = ambient + (diff + spec) * shadow; return col; } ENDCG } Pass { Tags { "LightMode" = "ForwardAdd" } Blend One One CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdadd #include "UnityCg.cginc" #include "Lighting.cginc" #include "AutoLight.cginc" sampler2D _MainTex; fixed4 _Specular; float _Gloss; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float4 worldPos: TEXCOORD2; SHADOW_COORDS(3) }; v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex); TRANSFER_SHADOW(o); return o; } fixed4 frag(v2f i) : SV_TARGET { fixed4 albedo = tex2D(_MainTex, i.uv); float3 worldLight = normalize(UnityWorldSpaceLightDir(i.worldPos.xyz)); float3 worldView = normalize(UnityWorldSpaceViewDir(i.worldPos.xyz)); fixed4 diff = albedo * _LightColor0 * max(0, dot(i.worldNormal, worldLight)); float3 halfDir = normalize(worldView + worldLight); fixed4 spec = albedo * _Specular * pow(max(0, dot(halfDir, i.worldNormal)), _Gloss); // 參考 AutoLight.cginc float atten; #ifdef USING_DIRECTIONAL_LIGHT atten = 1; #else float4 lightCoord = mul(unity_WorldToLight, i.worldPos); #ifdef POINT atten = tex2D(_LightTexture0, dot(lightCoord.xyz,lightCoord.xyz).xx).UNITY_ATTEN_CHANNEL; #elif SPOT atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL; #endif #endif float shadow = SHADOW_ATTENUATION(i); fixed4 col = (diff + spec) * atten * shadow; return col; } ENDCG } } Fallback "VertexLit" }
效果如下:

