1. 透明度测试物体的阴影
对于物体有片元丢弃的情况,比如透明度测试或者后边会讲到的消融效果,使用默认的 ShadowCaster Pass 会产生问题,这是因为该Pass在生成阴影映射纹理时,没有考虑被丢弃的片元,而是使用完整的模型计算深度,因此镂空的部分也会向外投射阴影。
要解决这个问题,可以使用Unity内置的Pass:
UsePass "Legacy Shaders/Transparent/Cutout/VertexLit/CASTER"
 
由于该Pass内部在计算阴影时需要用到特定名称的变量,因此在使用时,必须声明如下属性:
Properties
{
    _MainTex("MainTex", 2D) = "white"{}
    _Color("Color", Color) = (1, 1, 1, 1)
    _Cutoff("Cutoff", range(0, 1)) = 0
    }
 
除此以外,我们也可以自己实现一个 ShadowCaster 的 Pass,代码与上一篇【Unity Shader入门精要 第9章】更复杂的光照(三)中手动实现的 ShadowCaster 基本一样,只是在片元着色器中加入和正常渲染 Pass 中一样的剔除代码即可。
另外,由于镂空物体的内部也可能产生阴影,因此根据需要可以将物体的Cast Shadows选项选为Two Sided
 
测试Shader如下:
Shader "MyShader/Chapter_9/Chapter_9_Shadow_AlphaTest_Shader"
{
    Properties
    {
        _MainTex("MainTex", 2D) = "white"{}
        _Color("Color", Color) = (1, 1, 1, 1)
        _Cutoff("Cutoff", range(0, 1)) = 0
    }
    
    SubShader
    {
        Tags {"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
        
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            Cull Front
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            #pragma multi_compile_fwdbase
            
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                SHADOW_COORDS(3)
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;
            
            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                o.worldNormal = mul(i.normal, (float3x3)unity_WorldToObject);
                o.uv = TRANSFORM_TEX(i.uv, _MainTex);
                o.worldPos = mul(unity_ObjectToWorld, i.vertex).xyz;
                TRANSFER_SHADOW(o);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                float3 _worldNormal = normalize(i.worldNormal);
                float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed4 _sampledColor = tex2D(_MainTex, i.uv);
                clip(_sampledColor.w - _Cutoff);
                fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                fixed3 _diffuse = _LightColor0.rgb * _sampledColor.xyz * (0.5 * dot(_worldLight, _worldNormal) + 0.5);
                
                UNITY_LIGHT_ATTENUATION(_atten, i, i.worldPos);
                return fixed4(_ambient + _diffuse * _atten, 1);
            }
            
            ENDCG
        }
    
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            Cull Back
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            #pragma multi_compile_fwdbase
            
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                SHADOW_COORDS(3)
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;
            
            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                o.worldNormal = mul(i.normal, (float3x3)unity_WorldToObject);
                o.uv = TRANSFORM_TEX(i.uv, _MainTex);
                o.worldPos = mul(unity_ObjectToWorld, i.vertex).xyz;
                TRANSFER_SHADOW(o);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                float3 _worldNormal = normalize(i.worldNormal);
                float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed4 _sampledColor = tex2D(_MainTex, i.uv);
                clip(_sampledColor.w - _Cutoff);
                fixed3 _diffuse = _LightColor0.rgb * _sampledColor.xyz * (0.5 * dot(_worldLight, _worldNormal) + 0.5);
                
                UNITY_LIGHT_ATTENUATION(_atten, i, i.worldPos);
                return fixed4(_ambient + _diffuse * _atten, 1);
            }
            
            ENDCG
        }
        
        //使用内置的Pass处理透明度测试的物体向外投射阴影
        //UsePass "Legacy Shaders/Transparent/Cutout/VertexLit/CASTER"
    
    	//或者根据渲染逻辑手动实现符合当前效果的ShadowCaster的Pass
        Pass
        {
            Tags { "LightMode" = "ShadowCaster" }
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma multi_compile_shadowcaster
            
            struct a2v
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };
            
            struct v2f
            {
                V2F_SHADOW_CASTER;
                float2 uv : TEXCOORD0;
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;
            
            v2f vert(a2v v)
            {
                v2f o;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 _sampledColor = tex2D(_MainTex, i.uv);
                clip(_sampledColor.w - _Cutoff);
                SHADOW_CASTER_FRAGMENT(i);
            }
            
            ENDCG            
        }
    }
}
 
效果如下:
 
2. 透明度混合物体的阴影
2.1 投射阴影
透明度混合的Shader中关闭了深度写入,不会参与深度纹理的计算,因此不会向其他物体投射阴影。要使透明度测试的物体能够向外投射阴影方法有很多,比如放一个直接算阴影但不渲染自身的代替物体,或者像书中说的添加一个非Transparent的FallBack的Shader,再或者自己手写一个,都可以实现。
下面的例子就是将之前的阴影投射的Pass复制过来,也可以正常产生阴影。
Shader "MyShader/Chapter_9/Chapter_9_Shadow_AlphaBlend_Shader"
{
    Properties
    {
        _MainTex("MainTex", 2D) = "white"{}
    }
    
    SubShader
    {
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True"}
        
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            
            Cull Front
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                SHADOW_COORDS(3)
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                o.worldNormal = UnityObjectToWorldNormal(i.normal);
                o.worldPos = mul(unity_ObjectToWorld, i.vertex).xyz;
                o.uv = TRANSFORM_TEX(i.uv, _MainTex);
                TRANSFER_SHADOW(o);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                float3 _worldNormal = normalize(i.worldNormal);
                float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
                float4 _mainColor = tex2D(_MainTex, i.uv);
                fixed3 _diffuse = _LightColor0.rgb * _mainColor.xyz * (0.5 * dot(_worldLight, _worldNormal) + 0.5);
                
                UNITY_LIGHT_ATTENUATION(_atten, i, i.worldPos);
                return fixed4(_ambient + _diffuse * _atten, _mainColor.w);
            }
            
            ENDCG
        }
    
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            
            Cull Back
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
        
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                SHADOW_COORDS(3)
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                o.worldNormal = UnityObjectToWorldNormal(i.normal);
                o.worldPos = mul(unity_ObjectToWorld, i.vertex).xyz;
                o.uv = TRANSFORM_TEX(i.uv, _MainTex);
                TRANSFER_SHADOW(o);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 _ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                float3 _worldNormal = normalize(i.worldNormal);
                float3 _worldLight = normalize(_WorldSpaceLightPos0.xyz);
                float4 _mainColor = tex2D(_MainTex, i.uv);
                fixed3 _diffuse = _LightColor0.rgb * _mainColor.xyz * (0.5 * dot(_worldLight, _worldNormal) + 0.5);
                UNITY_LIGHT_ATTENUATION(_atten, i, i.worldPos);
                return fixed4(_ambient + _diffuse * _atten, _mainColor.w);
            }
            
            ENDCG
        }
        
        Pass
        {
            Tags { "LightMode" = "ShadowCaster" }
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma multi_compile_shadowcaster
            
            struct v2f
            {
                V2F_SHADOW_CASTER;
            };
            
            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i);
            }
            
            ENDCG
        }
    
    }
       
//    FallBack "VertexLit"
//    FallBack "Diffuse"
}
 
效果:
 
2.2 接收阴影
半透物体接收阴影就比较麻烦了,即使像书中说的将FallBack设置为 VertexLit,实际也是无法接收到阴影的(可以将上面测试shader中的注释放开试一下)。
这是因为正常半透物体的渲染队列为3000(Transparent),而在2500以后就没有阴影映射纹理的信息了,因此即使shader里有相应代码,也无法正常接收到阴影。
从下面 FrameDebug 的截图中也可以看出来:
 
 网上最多的说法是将渲染队列强制调到2500以下,比如像这样:
 
 
 这样虽然能接收到阴影,但显然不是正经做法,因为这样会造成半透物体的渲染顺序错乱,比如这样:
 
 要真正解决这个问题,内容就超出《入门精要》的范围了(也超出我的范围了)。
 这里先存本书,《实时阴影技术》,有时间开坑研究一下(应该是不会有时间了)。


















