GameTech/URP 渲染实战
源码 / shaders/Ch23/PlanarShadow.glsl
shaders/Ch23/PlanarShadow.glsl
// 案例 23.3:平面投影阴影(Planar Shadow)(GLSL 版)。
// 第一个 Pass 正常着色;第二个 Pass(SRPDefaultUnlit)把同一个网格沿主光方向“拍扁”到地面上,
// 输出半透明的阴影色,并用模板测试保证同一个像素只被阴影覆盖一次,避免重叠部分变黑。
Shader "Cookbook/Ch23/PlanarShadow"
{
    SubShader
    {
        // 排在普通不透明物体之后,保证地面已经画好,阴影才能混合到地面颜色上
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry+10" }

        GLSLINCLUDE
        uniform vec4  _BaseColor;
        uniform vec4  _ShadowColor;
        uniform float _PlaneHeight;
        uniform float _ShadowFalloff;
        ENDGLSL

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }
            GLSLPROGRAM
            VARYING vec3 normalWS;
            #ifdef VERTEX
            void main()
            {
                gl_Position = TransformObjectToHClip(positionOS.xyz);
                normalWS = TransformObjectToWorldNormal(normalOS);
            }
            #endif
            #ifdef FRAGMENT
            void main()
            {
                vec3 n = normalize(normalWS);
                Light l = GetMainLight();
                vec3 c = _BaseColor.rgb * (l.color * saturate(dot(n, l.direction)) + SampleSH(n));
                fragColor = vec4(c, 1);
            }
            #endif
            ENDGLSL
        }

        Pass
        {
            Name "PlanarShadow"
            Tags { "LightMode"="SRPDefaultUnlit" }   // URP 会把它和 UniversalForward 一起画

            Stencil
            {
                Ref 1
                Comp [_StencilComp]   // NotEqual:这个像素还没画过阴影才画
                Pass Replace          // 画过就把模板写成 1,后面的阴影三角形再来就被拒绝
            }
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Back

            GLSLPROGRAM
            VARYING float fade;

            #ifdef VERTEX
            void main()
            {
                vec3 positionWS = TransformObjectToWorld(positionOS.xyz);
                // _MainLightPosition.xyz 对方向光来说是“指向光源”的单位向量 L
                vec3 L = normalize(_MainLightPosition.xyz);

                // 求沿 -L 方向走多远能碰到平面 y = h:  P' = P - L * (P.y - h) / L.y
                float height = positionWS.y - _PlaneHeight;
                vec3 projected = positionWS - L * (height / max(L.y, 1e-3));
                projected.y = _PlaneHeight + 0.002;   // 抬高 2 毫米,避免与地面 Z-Fighting

                gl_Position = TransformWorldToHClip(projected);
                // 离地越高的部位投下的阴影越淡,模拟真实阴影随距离变软变淡
                fade = saturate(1.0 - height * _ShadowFalloff);
            }
            #endif

            #ifdef FRAGMENT
            void main()
            {
                fragColor = vec4(_ShadowColor.rgb, _ShadowColor.a * fade);
            }
            #endif
            ENDGLSL
        }

        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On ColorMask R
            GLSLPROGRAM
            #define CH23_DEPTH_PASS
            #include "Ch23/Ch23Common.glsl"
            ENDGLSL
        }
    }
}