GameTech/URP 渲染实战
源码 / Ch23/DitherShadow.shader
Ch23/DitherShadow.shader
// 案例 23.8:半透明物体的抖动阴影(Dithered Shadow)。
// 阴影贴图只存一个深度,表达不了“半透明”。办法是在 ShadowCaster 里按透明度用 Bayer 矩阵
// 有规律地丢掉一部分像素:alpha = 0.3 就大约只留 30% 的阴影像素,再经过软阴影 PCF 一平均,
// 接收端看到的就是“30% 浓度”的阴影。
Shader "Cookbook/Ch23/DitherShadow"
{
    Properties
    {
        [MainTexture] _BaseMap ("Base Map (A = 透明度)", 2D) = "white" {}
        [MainColor]   _BaseColor ("Base Color", Color) = (0.3, 0.7, 1, 0.4)
        [Toggle] _DitherShadow ("Dither Shadow", Float) = 1
        _ShadowAlphaScale ("Shadow Alpha Scale", Range(0, 2)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" "Queue"="Transparent" }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        CBUFFER_START(UnityPerMaterial)
            float4 _BaseMap_ST;
            half4  _BaseColor;
            half   _DitherShadow;
            half   _ShadowAlphaScale;
        CBUFFER_END
        TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
        ENDHLSL

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

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; };
            struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half3 normalWS : TEXCOORD1; float3 positionWS : TEXCOORD2; };
            Varyings vert(Attributes v)
            {
                Varyings o;
                VertexPositionInputs p = GetVertexPositionInputs(v.positionOS.xyz);
                o.positionCS = p.positionCS; o.positionWS = p.positionWS;
                o.normalWS = TransformObjectToWorldNormal(v.normalOS);
                o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
                return o;
            }
            half4 frag(Varyings i) : SV_Target
            {
                half4 tex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv) * _BaseColor;
                half3 n = normalize(i.normalWS);
                Light l = GetMainLight();
                half3 v = GetWorldSpaceNormalizeViewDir(i.positionWS);
                half3 h = normalize(l.direction + v);
                half spec = pow(saturate(dot(n, h)), 64) * 0.6;
                half3 c = tex.rgb * (l.color * saturate(dot(n, l.direction)) + SampleSH(n)) + spec * l.color;
                return half4(c, tex.a);
            }
            ENDHLSL
        }

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

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            #include "Assets/Cookbook/Ch23/Ch23Common.hlsl"

            struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; };
            struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; };

            Varyings vert(Attributes v)
            {
                Varyings o;
                CasterAttributes c;
                c.positionOS = v.positionOS;
                c.normalOS = v.normalOS;
                o.positionCS = Ch23ShadowCasterVert(c);
                o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
                return o;
            }

            // 4×4 Bayer 矩阵:相邻阈值尽量分散,丢弃的像素在空间上均匀
            static const half kBayer4x4[16] =
            {
                 0,  8,  2, 10,
                12,  4, 14,  6,
                 3, 11,  1,  9,
                15,  7, 13,  5
            };

            half4 frag(Varyings i) : SV_Target
            {
                half alpha = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv).a * _BaseColor.a * _ShadowAlphaScale;
                if (_DitherShadow > 0.5)
                {
                    // 在 ShadowCaster 里,SV_POSITION.xy 就是阴影贴图上的像素坐标
                    uint2 p = uint2(i.positionCS.xy) & 3;
                    half threshold = (kBayer4x4[p.y * 4 + p.x] + 0.5) / 16.0;
                    clip(alpha - threshold);
                }
                return 0;
            }
            ENDHLSL
        }
    }
}