GameTech/URP 渲染实战
源码 / shaders/Ch09/WhiteFurnace.glsl
shaders/Ch09/WhiteFurnace.glsl
// 第 9 章:白炉测试(White Furnace Test)。(GLSL 版,对应 Ch09/WhiteFurnace.shader)
// 假想物体被一个处处亮度相同的环境 L_env 包围,那么出射辐射率
//   L_o(v) = L_env · ∫ f(l,v) (n·l) dω_l = L_env · E(v)
// E(v) 就是 BRDF 的“方向反照率”。能量守恒要求 E ≤ 1;E = 1 时物体会和背景融为一体。
// 本 Shader 在每个像素上用蒙特卡洛积分直接算出 E(v),不依赖任何灯光。
Shader "Cookbook/Ch09/WhiteFurnace"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        Pass
        {
            Name "Furnace"
            Tags { "LightMode"="UniversalForward" }

            GLSLPROGRAM
            #include "Ch09/BRDFLib.glsl"

            uniform float _Model, _Roughness, _Albedo, _SampleCount;
            uniform vec4 _EnvColor;

            VARYING vec3 positionWS;
            VARYING vec3 normalWS;

            #ifdef VERTEX
            void main()
            {
                positionWS = TransformObjectToWorld(positionOS.xyz);
                gl_Position = TransformWorldToHClip(positionWS);
                normalWS = TransformObjectToWorldNormal(normalOS);
            }
            #endif

            #ifdef FRAGMENT
            // Hammersley 低差异序列:第二维是 i 的二进制位反转(Van der Corput 序列)
            float RadicalInverse(uint bits)
            {
                bits = (bits << 16u) | (bits >> 16u);
                bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
                bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
                bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
                bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
                return float(bits) * 2.3283064365386963e-10; // / 2^32
            }

            // 把切线空间方向转到以 N 为 z 轴的世界空间
            vec3 TangentToWorld(vec3 d, vec3 N)
            {
                vec3 up = abs(N.y) < 0.999 ? vec3(0, 1, 0) : vec3(1, 0, 0);
                vec3 T = normalize(cross(up, N));
                vec3 B = cross(N, T);
                return T * d.x + B * d.y + N * d.z;
            }

            float DirectionalAlbedo(vec3 N, vec3 V)
            {
                uint count = uint(_SampleCount);
                float NoV = max(dot(N, V), 1e-4);
                float a = _Roughness * _Roughness;
                float sum = 0.0;

                for (uint i = 0u; i < count; i++)
                {
                    vec2 xi = vec2((float(i) + 0.5) / float(count), RadicalInverse(i));

                    if (_Model < 0.5)
                    {
                        // Lambert:在半球上均匀采样,pdf = 1/(2π)
                        // 估计量 = f·cosθ / pdf = (ρ/π)·cosθ·2π
                        float cosT = xi.x, sinT = sqrt(1.0 - cosT * cosT), phi = TWO_PI * xi.y;
                        vec3 L = TangentToWorld(vec3(sinT * cos(phi), sinT * sin(phi), cosT), N);
                        sum += C9_Lambert(vec3(_Albedo)).x * saturate(dot(N, L)) * TWO_PI;
                    }
                    else
                    {
                        // GGX 重要性采样:按 D(h)(n·h) 的分布抽半程向量 h,再反射得到 l
                        // pdf(l) = D·(n·h) / (4 (v·h)),估计量化简为 G·F·(v·h) / ((n·v)(n·h)),这里 F = 1
                        float cosT = sqrt((1.0 - xi.x) / (1.0 + (a * a - 1.0) * xi.x));
                        float sinT = sqrt(1.0 - cosT * cosT), phi = TWO_PI * xi.y;
                        vec3 H = TangentToWorld(vec3(sinT * cos(phi), sinT * sin(phi), cosT), N);
                        vec3 L = reflect(-V, H);
                        float NoL = dot(N, L);
                        if (NoL > 0.0)
                        {
                            float NoH = saturate(dot(N, H)), VoH = saturate(dot(V, H));
                            float G = C9_G_SmithHeightCorrelated(NoL, NoV, a);
                            sum += G * VoH / (NoV * max(NoH, 1e-4));
                        }
                    }
                }
                return sum / float(count);
            }

            void main()
            {
                vec3 N = normalize(normalWS);
                vec3 V = GetWorldSpaceNormalizeViewDir(positionWS);
                float E = DirectionalAlbedo(N, V);
                fragColor = vec4(_EnvColor.rgb * E, 1.0);
            }
            #endif
            ENDGLSL
        }
    }
}