GameTech/URP 渲染实战
源码 / Ch25/GhostComposite.shader
Ch25/GhostComposite.shader
// 案例 25.7:把“幽灵层”RT 半透明地合成回屏幕,并与场景深度比较实现正确遮挡。
Shader "Cookbook/Ch25/GhostComposite"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" }
        ZTest Always ZWrite Off Cull Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            Name "GhostComposite"
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

            float4 _BlitTexture_TexelSize;
            TEXTURE2D_X_FLOAT(_GhostDepth);      // 幽灵层自己的深度
            float  _Opacity;
            float4 _Tint;
            float4 _RimColor;
            float  _RimWidth;

            half4 Frag(Varyings input) : SV_Target
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
                float2 uv = input.texcoord;
                half4 ghost = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, uv);

                // 1) 遮挡:幽灵层深度 与 场景深度 都换成线性深度再比较,与平台的 Reversed-Z 无关
                float ghostEye = LinearEyeDepth(SAMPLE_TEXTURE2D_X(_GhostDepth, sampler_PointClamp, uv).r, _ZBufferParams);
                float sceneEye = LinearEyeDepth(SampleSceneDepth(uv), _ZBufferParams);
                half visible = step(ghostEye, sceneEye + 0.01);

                // 2) 轮廓光:覆盖率(alpha)在邻域里的最小值 < 1 说明靠近剪影边缘
                float2 r = _BlitTexture_TexelSize.xy * _RimWidth;
                half minA = ghost.a;
                minA = min(minA, SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( r.x, 0)).a);
                minA = min(minA, SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(-r.x, 0)).a);
                minA = min(minA, SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(0,  r.y)).a);
                minA = min(minA, SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(0, -r.y)).a);
                half rim = ghost.a * (1.0 - minA);

                // 3) 整层统一透明度:内部的穿插面在离屏 RT 里已被深度测试剔除,合成时看不到
                half3 rgb = ghost.rgb * _Tint.rgb + _RimColor.rgb * rim;
                half alpha = saturate(ghost.a * _Opacity + rim) * visible;
                return half4(rgb, alpha);
            }
            ENDHLSL
        }
    }
}