源码 / Ch09/WhiteFurnace.shader
Ch09/WhiteFurnace.shader
// 第 9 章:白炉测试(White Furnace Test)。 // 假想物体被一个处处亮度相同的环境 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" { Properties { [Enum(Lambert,0,GGX_Specular_F1,1)] _Model("BRDF", Float) = 0 _Roughness("Perceptual Roughness", Range(0.02, 1)) = 0.5 _Albedo("Albedo", Range(0, 1)) = 1 _EnvColor("Environment Radiance", Color) = (0.5, 0.5, 0.5, 1) _SampleCount("Sample Count", Float) = 512 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" } Pass { Name "Furnace" Tags { "LightMode"="UniversalForward" } HLSLPROGRAM #pragma target 4.5 #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Assets/Cookbook/Ch09/BRDFLib.hlsl" CBUFFER_START(UnityPerMaterial) float _Model, _Roughness, _Albedo, _SampleCount; float4 _EnvColor; CBUFFER_END struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; }; struct Varyings { float4 positionCS : SV_POSITION; float3 positionWS : TEXCOORD0; float3 normalWS : TEXCOORD1; }; Varyings vert(Attributes v) { Varyings o; o.positionWS = TransformObjectToWorld(v.positionOS.xyz); o.positionCS = TransformWorldToHClip(o.positionWS); o.normalWS = TransformObjectToWorldNormal(v.normalOS); return o; } // 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 轴的世界空间 float3 TangentToWorld(float3 d, float3 N) { float3 up = abs(N.y) < 0.999 ? float3(0, 1, 0) : float3(1, 0, 0); float3 T = normalize(cross(up, N)); float3 B = cross(N, T); return T * d.x + B * d.y + N * d.z; } float DirectionalAlbedo(float3 N, float3 V) { uint count = (uint)_SampleCount; float NoV = max(dot(N, V), 1e-4); float a = _Roughness * _Roughness; float sum = 0; for (uint i = 0; i < count; i++) { float2 xi = float2((i + 0.5) / count, RadicalInverse(i)); if (_Model < 0.5) { // Lambert:在半球上均匀采样,pdf = 1/(2π) // 估计量 = f·cosθ / pdf = (ρ/π)·cosθ·2π float cosT = xi.x, sinT = sqrt(1 - cosT * cosT), phi = TWO_PI * xi.y; float3 L = TangentToWorld(float3(sinT * cos(phi), sinT * sin(phi), cosT), N); sum += C9_Lambert(_Albedo.xxx).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 - xi.x) / (1 + (a * a - 1) * xi.x)); float sinT = sqrt(1 - cosT * cosT), phi = TWO_PI * xi.y; float3 H = TangentToWorld(float3(sinT * cos(phi), sinT * sin(phi), cosT), N); float3 L = reflect(-V, H); float NoL = dot(N, L); if (NoL > 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 / count; } half4 frag(Varyings i) : SV_Target { float3 N = normalize(i.normalWS); float3 V = GetWorldSpaceNormalizeViewDir(i.positionWS); float E = DirectionalAlbedo(N, V); return half4(_EnvColor.rgb * E, 1); } ENDHLSL } } }