GameTech/URP 渲染实战
源码 / Ch12/Translucent.shader
Ch12/Translucent.shader
// 薄片透射:树叶、耳朵、纸灯笼一类“光能从背面透过来”的薄物体。
// 透射项用 Barré-Brisebois & Bouchard (GDC 2011) 的快速近似,厚度来自程序化叶脉。
Shader "Cookbook/Ch12/Translucent"
{
    Properties
    {
        [MainColor] _BaseColor ("Leaf Color", Color) = (0.18, 0.42, 0.08, 1)
        _VeinColor ("Vein Color", Color) = (0.45, 0.6, 0.2, 1)
        [Toggle] _Transmission ("Transmission", Float) = 1
        [Toggle] _UseThickness ("Use Thickness (veins)", Float) = 1
        _TransColor ("Transmission Color", Color) = (0.75, 0.95, 0.2, 1)
        _Distortion ("Normal Distortion", Range(0, 1)) = 0.3
        _TransPower ("Power", Range(1, 16)) = 4
        _TransScale ("Scale", Range(0, 8)) = 2.5
        _TransAmbient ("Ambient Transmission", Range(0, 1)) = 0.15
        _Smoothness ("Smoothness", Range(0, 1)) = 0.55
    }

    SubShader
    {
        Tags { "RenderType"="TransparentCutout" "RenderPipeline"="UniversalPipeline" "Queue"="AlphaTest" }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
        CBUFFER_START(UnityPerMaterial)
            half4 _BaseColor, _VeinColor, _TransColor;
            half  _Transmission, _UseThickness, _Distortion, _TransPower, _TransScale, _TransAmbient, _Smoothness;
        CBUFFER_END

        // 程序化叶片:UV 的 y 从叶柄(0)到叶尖(1)。返回 x=是否在叶内(符号距离),y=叶脉强度(0~1)
        float2 LeafShape(float2 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 = frac((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 float2(inside, saturate(max(mid, side * 0.65)));
        }
        ENDHLSL

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

            HLSLPROGRAM
            #pragma target 3.5
            #pragma vertex Ch12Vert
            #pragma fragment frag
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
            #pragma multi_compile _ _ADDITIONAL_LIGHTS
            #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
            #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH
            #pragma multi_compile_fragment _ _LIGHT_COOKIES
            #pragma multi_compile _ _FORWARD_PLUS
            #pragma multi_compile_fog
            #pragma multi_compile_instancing

            #include "Assets/Cookbook/Ch12/Ch12Common.hlsl"

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

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

                // 透射:把光方向沿法线“扭”一点(模拟光在叶片内部的散射偏折),
                // 再看视线与 -L' 的接近程度——正对着光源看时最亮
                float3 Lt = normalize(L + m.N * _Distortion);
                float VoLt = pow(saturate(dot(m.V, -Lt)), _TransPower) * _TransScale;
                float3 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 "Assets/Cookbook/Ch12/LightLoop.hlsl"

            half4 frag(Varyings i, bool isFront : SV_IsFrontFace) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                float2 leaf = LeafShape(i.uv);
                clip(leaf.x);

                float3 T, B, N; Ch12BuildFrame(i, T, B, N);
                N = isFront ? N : -N;   // 背面要翻转法线,否则背面的光照是反的
                float3 V = GetWorldSpaceNormalizeViewDir(i.positionWS);
                float2 screenUV = GetNormalizedScreenSpaceUV(i.positionCS);

                MaterialCtx m;
                m.N = N; m.V = V;
                m.albedo = lerp(_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);

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

                color = MixFog(color, i.fogFactor);
                return half4(color, 1);
            }
            ENDHLSL
        }

        // 叶片形状是程序化裁剪的,URP 自带的 ShadowCaster 不知道,所以自己写一个带 clip 的
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On ZTest LEqual ColorMask 0 Cull Off
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
            float3 _LightDirection;
            float3 _LightPosition;
            struct A { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID };
            struct V { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; };
            V vert(A v)
            {
                UNITY_SETUP_INSTANCE_ID(v);
                V o;
                float3 positionWS = TransformObjectToWorld(v.positionOS.xyz);
                float3 normalWS = TransformObjectToWorldNormal(v.normalOS);
            #if _CASTING_PUNCTUAL_LIGHT_SHADOW
                float3 L = normalize(_LightPosition - positionWS);
            #else
                float3 L = _LightDirection;
            #endif
                o.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, L));
            #if UNITY_REVERSED_Z
                o.positionCS.z = min(o.positionCS.z, UNITY_NEAR_CLIP_VALUE);
            #else
                o.positionCS.z = max(o.positionCS.z, UNITY_NEAR_CLIP_VALUE);
            #endif
                o.uv = v.uv;
                return o;
            }
            half4 frag(V i) : SV_Target { clip(LeafShape(i.uv).x); return 0; }
            ENDHLSL
        }
        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On ColorMask R Cull Off
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            struct A { float4 positionOS : POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID };
            struct V { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; };
            V vert(A v) { UNITY_SETUP_INSTANCE_ID(v); V o; o.positionCS = TransformObjectToHClip(v.positionOS.xyz); o.uv = v.uv; return o; }
            half frag(V i) : SV_Target { clip(LeafShape(i.uv).x); return i.positionCS.z; }
            ENDHLSL
        }
    }
}