GameTech/URP 渲染实战
源码 / Ch26/MyBloom.shader
Ch26/MyBloom.shader
// 案例 26.4:自制 Bloom。Pass 0 预过滤,1 下采样,2 上采样合并,3 合成。
Shader "Cookbook/Ch26/MyBloom"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        ZTest Always ZWrite Off Cull Off

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

        float4 _BlitTexture_TexelSize;
        float4 _BloomParams;            // x 阈值, y 膝宽, z scatter, w 强度
        float4 _BloomTint;
        float  _BloomDebugView;         // 0 正常, 1 只看预过滤, 2 只看泛光
        TEXTURE2D_X(_BloomLowMip);      // 上采样时的“下一层”(全局纹理)
        TEXTURE2D_X(_BloomTex);         // 合成时的最终泛光(全局纹理)

        half3 Tap(float2 uv) { return SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv).rgb; }

        half4 FragPrefilter(Varyings input) : SV_Target
        {
            UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
            float2 uv = input.texcoord;
            float2 t = _BlitTexture_TexelSize.xy;
            // 4 次双线性 = 4×4 纹素的盒式平均,顺便完成 2 倍降采样
            half3 c = (Tap(uv + t * float2(-1, -1)) + Tap(uv + t * float2(1, -1))
                     + Tap(uv + t * float2(-1,  1)) + Tap(uv + t * float2(1,  1))) * 0.25;
            c = min(c, 65000.0);                       // 防止 Inf 一路扩散

            // 软阈值(二次曲线过渡的“膝”):亮度 b 在 [T-K, T+K] 之间平滑过渡
            half threshold = _BloomParams.x, knee = _BloomParams.y;
            half b = Max3(c.r, c.g, c.b);
            half soft = clamp(b - threshold + knee, 0.0, 2.0 * knee);
            soft = soft * soft / (4.0 * knee + 1e-4);
            half contribution = max(b - threshold, soft) / max(b, 1e-4);
            return half4(c * contribution, 1.0);
        }

        // 13 次采样下采样(Jimenez 2014, Next Generation Post Processing in Call of Duty):
        // 中心 4 点权重 0.5,四个角上的 2×2 小方块各 0.125,能明显抑制高亮像素的闪烁
        half4 FragDown(Varyings input) : SV_Target
        {
            UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
            float2 uv = input.texcoord;
            float2 t = _BlitTexture_TexelSize.xy;
            half3 A = Tap(uv + t * float2(-2, -2)), B = Tap(uv + t * float2(0, -2)), C = Tap(uv + t * float2(2, -2));
            half3 D = Tap(uv + t * float2(-1, -1)), E = Tap(uv + t * float2(1, -1));
            half3 F = Tap(uv + t * float2(-2,  0)), G = Tap(uv),                      H = Tap(uv + t * float2(2,  0));
            half3 I = Tap(uv + t * float2(-1,  1)), J = Tap(uv + t * float2(1,  1));
            half3 K = Tap(uv + t * float2(-2,  2)), L = Tap(uv + t * float2(0,  2)), M = Tap(uv + t * float2(2,  2));
            half3 c = (D + E + I + J) * (0.5 / 4.0);
            c += (A + B + G + F) * (0.125 / 4.0);
            c += (B + C + H + G) * (0.125 / 4.0);
            c += (F + G + L + K) * (0.125 / 4.0);
            c += (G + H + M + L) * (0.125 / 4.0);
            return half4(c, 1.0);
        }

        // 上采样:低一层用 3×3 帐篷滤波放大,再与本层按 scatter 插值
        half4 FragUp(Varyings input) : SV_Target
        {
            UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
            float2 uv = input.texcoord;
            float2 t = _BlitTexture_TexelSize.xy;
            #define LOW(o) SAMPLE_TEXTURE2D_X(_BloomLowMip, sampler_LinearClamp, uv + t * (o)).rgb
            half3 low = LOW(float2(-1, -1)) + LOW(float2(1, -1)) + LOW(float2(-1, 1)) + LOW(float2(1, 1));
            low += (LOW(float2(0, -1)) + LOW(float2(-1, 0)) + LOW(float2(1, 0)) + LOW(float2(0, 1))) * 2.0;
            low += LOW(float2(0, 0)) * 4.0;
            low *= 1.0 / 16.0;
            #undef LOW
            half3 high = Tap(uv);
            return half4(lerp(high, low, _BloomParams.z), 1.0);
        }

        half4 FragComposite(Varyings input) : SV_Target
        {
            UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
            float2 uv = input.texcoord;
            half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
            half3 bloom = SAMPLE_TEXTURE2D_X(_BloomTex, sampler_LinearClamp, uv).rgb * _BloomTint.rgb * _BloomParams.w;
            if (_BloomDebugView > 0.5) return half4(bloom, 1.0);   // 调试:只看泛光
            color.rgb += bloom;                                     // 线性 HDR 空间里直接相加
            return color;
        }
        ENDHLSL

        Pass { Name "Prefilter"
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment FragPrefilter
            ENDHLSL
        }
        Pass { Name "Down"
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment FragDown
            ENDHLSL
        }
        Pass { Name "Up"
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment FragUp
            ENDHLSL
        }
        Pass { Name "Composite"
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment FragComposite
            ENDHLSL
        }
    }
}