GameTech/URP 渲染实战
源码 / shaders/Ch28/VolumetricLight.glsl
shaders/Ch28/VolumetricLight.glsl
// 案例 28.5:体积光(主光方向光的光束 / God Rays)。
// Pass 0:半分辨率光线步进,逐步采样主光阴影图;Pass 1/2:可分离模糊;Pass 3:合成到相机颜色;Pass 4:只看散射项
Shader "Cookbook/Ch28/VolumetricLight"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        ZWrite Off ZTest Always Cull Off Blend Off

        GLSLINCLUDE
        #include "Ch28/ScreenSpaceCommon.glsl"
        #define FOUR_PI 12.5663706144
        #define INV_FOUR_PI 0.07957747155

        uniform float _Density, _Anisotropy, _MaxDistance, _Steps, _Intensity, _Jitter;
        uniform vec4 _Tint;
        uniform vec4 _VolTexelSize;           // 体积纹理的 (1/w, 1/h, w, h)
        uniform sampler2D _VolumetricResult;  // 合成时读取(r = 散射,g = 透射率)

        vec4 Blur9(vec2 uv, vec2 dir)
        {
            const float w[5] = float[5](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
            vec4 sum = texture(_BlitTexture, uv) * w[0];
            for (int i = 1; i < 5; i++)
            {
                sum += texture(_BlitTexture, uv + dir * float(i)) * w[i];
                sum += texture(_BlitTexture, uv - dir * float(i)) * w[i];
            }
            return sum;
        }
        ENDGLSL

        Pass
        {
            Name "RayMarch"
            GLSLPROGRAM
            // 主光阴影的关键字(_MAIN_LIGHT_SHADOWS 等)网页版始终开启,不用声明。
            // 不需要 _MAIN_LIGHT_SHADOWS_SCREEN:屏幕空间阴影只有“表面”上的结果,体积里的点用不上。
            #ifdef FRAGMENT
            float InterleavedGradientNoise(vec2 pixCoord, int frameCount)
            {
                pixCoord += float(frameCount) * vec2(2.083, 4.867);
                return fract(52.9829189 * fract(dot(pixCoord, vec2(0.06711056, 0.00583715))));
            }

            // Henyey-Greenstein 相位函数:g>0 时向前散射为主,迎着光看最亮
            float PhaseHG(float cosTheta, float g)
            {
                float g2 = g * g;
                return INV_FOUR_PI * (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosTheta, 1e-4), 1.5);
            }

            vec4 FragMarch()
            {
                vec2 uv = texcoord;
                float depth = SampleDeviceDepth(uv);
                vec3 endWS = IsSkyDepth(depth)
                    ? ComputeWorldSpacePosition(uv, UNITY_RAW_FAR_CLIP_VALUE, UNITY_MATRIX_I_VP)
                    : ReconstructPositionWS(uv, depth);

                vec3 rayWS = endWS - _WorldSpaceCameraPos;
                float rayLength = min(length(rayWS), _MaxDistance);
                vec3 dir = rayWS / max(length(rayWS), 1e-4);

                Light mainLight = GetMainLight();
                float phase = PhaseHG(dot(dir, mainLight.direction), _Anisotropy);

                int steps = int(_Steps);
                float stepLength = rayLength / float(steps);
                // 抖动起点:把“条带”换成高频噪声,再交给模糊去掉
                float jitter = _Jitter > 0.5 ? InterleavedGradientNoise(gl_FragCoord.xy, 0) : 0.5;

                float scatter = 0.0;
                float transmittance = 1.0;
                float stepT = exp(-_Density * stepLength);    // 每步的透射率(均匀介质)
                for (int i = 0; i < steps; i++)
                {
                    float t = (float(i) + jitter) * stepLength;
                    vec3 p = _WorldSpaceCameraPos + dir * t;
                    // 关键两行:世界坐标 → 阴影坐标(自动选级联)→ 比较阴影图
                    vec4 shadowCoord = TransformWorldToShadowCoord(p);
                    float visibility = MainLightRealtimeShadow(shadowCoord);
                    // 这一小段介质散射到相机方向的光,再乘上它到相机之间的透射率
                    scatter += visibility * transmittance * (1.0 - stepT);
                    transmittance *= stepT;
                }
                scatter *= phase * _Intensity * FOUR_PI;  // ×4π 让 g=0 时的强度约为 1,便于调参
                return vec4(scatter, transmittance, 0.0, 1.0);
            }
            void main() { fragColor = FragMarch(); }
            #endif
            ENDGLSL
        }

        Pass
        {
            Name "BlurH"
            GLSLPROGRAM
            #ifdef FRAGMENT
            void main() { fragColor = Blur9(texcoord, vec2(_VolTexelSize.x, 0.0)); }
            #endif
            ENDGLSL
        }
        Pass
        {
            Name "BlurV"
            GLSLPROGRAM
            #ifdef FRAGMENT
            void main() { fragColor = Blur9(texcoord, vec2(0.0, _VolTexelSize.y)); }
            #endif
            ENDGLSL
        }
        Pass
        {
            Name "Composite"
            GLSLPROGRAM
            #ifdef FRAGMENT
            void main()
            {
                vec4 color = SamplePointClamp(_BlitTexture, texcoord);
                vec2 vol = texture(_VolumetricResult, texcoord).rg;
                vec3 lightColor = GetMainLight().color * _Tint.rgb;
                // 场景被介质衰减(×透射率),再加上介质散射进视线的光
                color.rgb = color.rgb * vol.g + lightColor * vol.r;
                fragColor = color;
            }
            #endif
            ENDGLSL
        }
        Pass
        {
            Name "ShowScatter"
            GLSLPROGRAM
            #ifdef FRAGMENT
            void main()
            {
                float s = texture(_VolumetricResult, texcoord).r;
                fragColor = vec4(vec3(s), 1.0);
            }
            #endif
            ENDGLSL
        }
    }
}