GameTech/URP 渲染实战
源码 / shaders/Ch23/DitherShadow.glsl
shaders/Ch23/DitherShadow.glsl
// 案例 23.8:半透明物体的抖动阴影(Dithered Shadow)(GLSL 版)。
// 阴影贴图只存一个深度,表达不了“半透明”。办法是在 ShadowCaster 里按透明度用 Bayer 矩阵
// 有规律地丢掉一部分像素:alpha = 0.3 就大约只留 30% 的阴影像素,再经过软阴影 PCF 一平均,
// 接收端看到的就是“30% 浓度”的阴影。
Shader "Cookbook/Ch23/DitherShadow"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" "Queue"="Transparent" }

        GLSLINCLUDE
        uniform vec4  _BaseMap_ST;
        uniform vec4  _BaseColor;
        uniform float _DitherShadow;
        uniform float _ShadowAlphaScale;
        uniform sampler2D _BaseMap;
        ENDGLSL

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Back

            GLSLPROGRAM
            VARYING vec2 uv;
            VARYING vec3 normalWS;
            VARYING vec3 positionWS;
            #ifdef VERTEX
            void main()
            {
                VertexPositionInputs p = GetVertexPositionInputs(positionOS.xyz);
                gl_Position = p.positionCS; positionWS = p.positionWS;
                normalWS = TransformObjectToWorldNormal(normalOS);
                uv = TransformTex(texcoord0.xy, _BaseMap_ST);
            }
            #endif
            #ifdef FRAGMENT
            void main()
            {
                vec4 tex = texture(_BaseMap, uv) * _BaseColor;
                vec3 n = normalize(normalWS);
                Light l = GetMainLight();
                vec3 v = GetWorldSpaceNormalizeViewDir(positionWS);
                vec3 h = normalize(l.direction + v);
                float spec = pow(saturate(dot(n, h)), 64.0) * 0.6;
                vec3 c = tex.rgb * (l.color * saturate(dot(n, l.direction)) + SampleSH(n)) + spec * l.color;
                fragColor = vec4(c, tex.a);
            }
            #endif
            ENDGLSL
        }

        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On ZTest LEqual ColorMask 0
            Cull Back

            GLSLPROGRAM
            #include "Ch23/Ch23Common.glsl"
            VARYING vec2 uv;

            #ifdef VERTEX
            void main()
            {
                gl_Position = Ch23ShadowCasterVert();
                uv = TransformTex(texcoord0.xy, _BaseMap_ST);
            }
            #endif

            #ifdef FRAGMENT
            // 4×4 Bayer 矩阵:相邻阈值尽量分散,丢弃的像素在空间上均匀
            const float kBayer4x4[16] = float[16](
                 0.0,  8.0,  2.0, 10.0,
                12.0,  4.0, 14.0,  6.0,
                 3.0, 11.0,  1.0,  9.0,
                15.0,  7.0, 13.0,  5.0
            );

            void main()
            {
                float alpha = texture(_BaseMap, uv).a * _BaseColor.a * _ShadowAlphaScale;
                if (_DitherShadow > 0.5)
                {
                    // 在 ShadowCaster 里,gl_FragCoord.xy 就是阴影贴图上的像素坐标
                    uvec2 p = uvec2(gl_FragCoord.xy) & 3u;
                    float threshold = (kBayer4x4[p.y * 4u + p.x] + 0.5) / 16.0;
                    if (alpha - threshold < 0.0) discard;
                }
                fragColor = vec4(0.0);
            }
            #endif
            ENDGLSL
        }
    }
}