GameTech/URP 渲染实战
源码 / shaders/Ch12/Translucent.glsl
shaders/Ch12/Translucent.glsl
// 薄片透射:树叶、耳朵、纸灯笼一类“光能从背面透过来”的薄物体。
// 透射项用 Barré-Brisebois & Bouchard (GDC 2011) 的快速近似,厚度来自程序化叶脉。
// (GLSL 版,对应 Ch12/Translucent.shader)
Shader "Cookbook/Ch12/Translucent"
{
    SubShader
    {
        Tags { "RenderType"="TransparentCutout" "RenderPipeline"="UniversalPipeline" "Queue"="AlphaTest" }

        GLSLINCLUDE
        uniform vec4  _BaseColor, _VeinColor, _TransColor;
        uniform float _Transmission, _UseThickness, _Distortion, _TransPower, _TransScale, _TransAmbient, _Smoothness;

        // 程序化叶片:UV 的 y 从叶柄(0)到叶尖(1)。返回 x=是否在叶内(符号距离),y=叶脉强度(0~1)
        vec2 LeafShape(vec2 uv)
        {
            float x = uv.x - 0.5, y = uv.y;
            float halfWidth = 0.44 * pow(saturate(sin(PI * y)), 0.75) * (1.0 - 0.35 * y);
            float inside = halfWidth - abs(x);
            float mid = 1.0 - smoothstep(0.004, 0.018, abs(x));                       // 主脉
            float f = fract((y - abs(x) * 0.9) * 7.0);                                // 斜向重复的侧脉
            float side = 1.0 - smoothstep(0.03, 0.09, min(f, 1.0 - f));
            side *= smoothstep(0.02, 0.06, abs(x)) * smoothstep(0.0, 0.1, inside);    // 侧脉,离边缘淡出
            return vec2(inside, saturate(max(mid, side * 0.65)));
        }
        ENDGLSL

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForwardOnly" }
            Cull Off   // 叶片双面可见

            GLSLPROGRAM
            #include "Ch12/Ch12Common.glsl"

            #ifdef VERTEX
            void main() { Ch12Vert(); }
            #endif

            #ifdef FRAGMENT
            struct MaterialCtx
            {
                vec3 N, V;
                vec3 albedo;
                float thickness;   // 0 = 极薄,1 = 不透光
                float a;
            };

            vec3 ShadeOneLight(MaterialCtx m, Light light)
            {
                vec3 L = light.direction;
                float NoL = saturate(dot(m.N, L));
                vec3 front = (m.albedo * ADV_INV_PI + AdvSpecularGGX(m.N, m.V, L, m.a, 0.04)) * PI * NoL;

                // 透射:把光方向沿法线“扭”一点(模拟光在叶片内部的散射偏折),
                // 再看视线与 -L' 的接近程度——正对着光源看时最亮
                vec3 Lt = normalize(L + m.N * _Distortion);
                float VoLt = pow(saturate(dot(m.V, -Lt)), _TransPower) * _TransScale;
                vec3 trans = (VoLt + _TransAmbient) * (1.0 - m.thickness) * _TransColor.rgb * m.albedo * _Transmission;

                // 透射光走的是“叶片背面”的阴影:正面阴影图对它没有意义,这里只用距离衰减
                return front * light.color * (light.distanceAttenuation * light.shadowAttenuation)
                     + trans * light.color * light.distanceAttenuation;
            }

            #include "Ch12/LightLoop.glsl"

            void main()
            {
                vec2 leaf = LeafShape(uv);
                if (leaf.x < 0.0) discard;

                vec3 T, B, N; Ch12BuildFrame(T, B, N);
                N = gl_FrontFacing ? N : -N;   // 背面要翻转法线,否则背面的光照是反的
                vec3 V = GetWorldSpaceNormalizeViewDir(positionWS);
                vec2 screenUV = GetNormalizedScreenSpaceUV(gl_FragCoord);

                MaterialCtx m;
                m.N = N; m.V = V;
                m.albedo = mix(_BaseColor.rgb, _VeinColor.rgb, leaf.y * 0.6);
                m.thickness = _UseThickness > 0.5 ? leaf.y * 0.85 : 0.0;
                float pr = 1.0 - _Smoothness;
                m.a = max(pr * pr, 0.002);

                vec3 color = AccumulateDirectLighting(m, positionWS, screenUV);
                color += SampleSH(N) * m.albedo;

                color = MixFog(color, fogFactor);
                fragColor = vec4(color, 1.0);
            }
            #endif
            ENDGLSL
        }

        // 叶片形状是程序化裁剪的,URP 自带的 ShadowCaster 不知道,所以自己写一个带 clip 的
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On ZTest LEqual ColorMask 0 Cull Off
            GLSLPROGRAM
            VARYING vec2 uv;
            #ifdef VERTEX
            void main()
            {
                vec3 positionWS = TransformObjectToWorld(positionOS.xyz);
                vec3 normalWS = TransformObjectToWorldNormal(normalOS);
                // _CASTING_PUNCTUAL_LIGHT_SHADOW 在 GLSL 版里是个 uniform
                vec3 L = _CastingPunctualLightShadow > 0.5 ? normalize(_LightPosition - positionWS) : _LightDirection;
                vec4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, L));
                // WebGL 深度不反转(UNITY_REVERSED_Z = 0),走 max 分支
                positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE * positionCS.w);
                uv = texcoord0.xy;
                gl_Position = positionCS;
            }
            #endif
            #ifdef FRAGMENT
            void main() { if (LeafShape(uv).x < 0.0) discard; fragColor = vec4(0.0); }
            #endif
            ENDGLSL
        }
        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On ColorMask R Cull Off
            GLSLPROGRAM
            VARYING vec2 uv;
            #ifdef VERTEX
            void main() { gl_Position = TransformObjectToHClip(positionOS.xyz); uv = texcoord0.xy; }
            #endif
            #ifdef FRAGMENT
            void main() { if (LeafShape(uv).x < 0.0) discard; fragColor = vec4(gl_FragCoord.z); }
            #endif
            ENDGLSL
        }
    }
}