diff options
| author | yum <yum.food.vr@gmail.com> | 2025-08-08 19:14:38 -0700 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2025-08-08 19:14:38 -0700 |
| commit | 7cdf39f51b47553af89c3de5b86d33ded4b5cdd2 (patch) | |
| tree | 47fdf40b13ada0ea52b545b941b0acc29066e448 /brdf.cginc | |
| parent | 310dbc3a5393635e08faf78ddf47c57a37524d4b (diff) | |
Add cloth lobe & IBL DFG LUTs
- add monte carlo integrator to produce the luts
- fix layer energy integration; should be multiplicative, not additive
Diffstat (limited to 'brdf.cginc')
| -rw-r--r-- | brdf.cginc | 124 |
1 files changed, 106 insertions, 18 deletions
@@ -50,11 +50,70 @@ float D_GGX(float roughness, float NoH) { // denominator of the specular lobe because of some cancellations. // The original, un-optimized equation is: // 2 * NoL * NoV / lerp(2 * NoL * NoV, NoL + NoV, roughness) -float V_GGXSmith(float roughness, float NoL, float NoV) { +float G_GGXSmith(float roughness, float NoL, float NoV) { float denom = 2.0f * lerp(2.0f * NoL * NoV, NoL + NoV, roughness); return rcp(denom); } +// Estevez "Production Friendly Microfacet Sheen BRDF" +// Equation 2. +// The original equation is: +// (2 + 1/r) * sin^(1-r)(theta) / (2 pi) +// Recall that: +// cos^2(theta) + sin^2(theta) = 1 +// So: +// sin^2(theta) = 1 - cos^2(theta) +// sin(theta) = (1 - cos^2(theta)) ^ (1/2) +// sin^k(theta) = (1 - cos^2(theta)) ^ (k/2) +float D_Cloth(float roughness, float NoH) { + float r_rcp = rcp(roughness); + return (2.0f + r_rcp) * pow(1.0f - NoH * NoH, r_rcp * 0.5f) / TAU; +} + +float G_Cloth_L(float x, float a, float b, float c, float d, float e) { + return a / (1.0f + b * pow(x, c)) + d * x + e; +} + +// Estevez "Production Friendly Microfacet Sheen BRDF" +// Equations 3 and 4. +float G_Cloth(float roughness, float LoH) { + // Table 1 + float a0 = 25.3245f; + float a1 = 21.5473f; + float b0 = 3.32435f; + float b1 = 3.82987f; + float c0 = 0.16801f; + float c1 = 0.19823f; + float d0 = -1.27393f; + float d1 = -1.97760f; + float e0 = -4.85967f; + float e1 = -4.32054f; + float one_minus_r = 1.0f - roughness; + float one_minus_r_sq = one_minus_r * one_minus_r; + float one_minus_LoH = 1.0f - LoH; + + float lambda; + [branch] + if (LoH < 0.5f) { + float L0 = G_Cloth_L(LoH, a0, b0, c0, d0, e0); + float L1 = G_Cloth_L(LoH, a1, b1, c1, d1, e1); + float L = lerp(L0, L1, one_minus_r_sq); + lambda = exp(L); + } else { + float L_05_0 = G_Cloth_L(0.5f, a0, b0, c0, d0, e0); + float L_05_1 = G_Cloth_L(0.5f, a1, b1, c1, d1, e1); + float L_05 = lerp(L_05_0, L_05_1, one_minus_r_sq); + + float L_LoH_0 = G_Cloth_L(one_minus_LoH, a0, b0, c0, d0, e0); + float L_LoH_1 = G_Cloth_L(one_minus_LoH, a1, b1, c1, d1, e1); + float L_LoH = lerp(L_LoH_0, L_LoH_1, one_minus_r_sq); + + lambda = exp(2.0f * L_05 - L_LoH); + } + // Apply terminator softening (equation 4). + return pow(lambda, 1.0f + 2.0f * pow(one_minus_LoH, 8)); +} + float4 brdf(Pbr pbr, LightData data) { float3 specular = 0; float3 diffuse = 0; @@ -80,19 +139,31 @@ float4 brdf(Pbr pbr, LightData data) { #if defined(_CLEARCOAT) float cc_f0 = 0.04f; - float Fc = F_Schlick(data.direct.LoH, cc_f0, f90); - float Dc = D_GGX(pbr.cc_roughness, data.direct.NoH_cc); - float Gc = V_GGXSmith(pbr.cc_roughness, data.direct.NoL_cc, data.common.NoV_cc); - float FDGc = Fc * Dc * Gc; - float3 direct_specular_cc = FDGc * data.direct.color * data.direct.NoL_cc * pbr.cc_strength; + float Fcc = F_Schlick(data.direct.LoH, cc_f0, f90); + float Dcc = D_GGX(pbr.cc_roughness, data.direct.NoH_cc); + float Gcc = G_GGXSmith(pbr.cc_roughness, data.direct.NoL_cc, data.common.NoV_cc); + float DFGcc = Fcc * Dcc * Gcc; + float3 direct_specular_cc = DFGcc * data.direct.color * data.direct.NoL_cc * pbr.cc_strength; direct_specular_cc = max(0, direct_specular_cc); specular += direct_specular_cc; - remainder -= Fc * pbr.cc_strength; + remainder -= Fcc * pbr.cc_strength; +#endif + +#if defined(_CLOTH_SHEEN) + float cl_f0 = 0.04f; + float Fcl = 1; + float Dcl = D_Cloth(pbr.roughness, data.direct.NoH); + float Gcl = G_Cloth(pbr.roughness, data.direct.LoH); + float DFGcl = Fcl * Dcl * Gcl; + float3 direct_specular_cl = DFGcl * data.direct.color * pbr.cl_strength * pbr.cl_color * data.direct.NoL; + direct_specular_cl = max(0, direct_specular_cl); + specular += direct_specular_cl; + remainder -= direct_specular_cl; #endif float F = F_Schlick(data.direct.LoH, f0, f90); float D = D_GGX(pbr.roughness, data.direct.NoH); - float G = V_GGXSmith(pbr.roughness, data.direct.NoL, data.common.NoV); + float G = G_GGXSmith(pbr.roughness, data.direct.NoL, data.common.NoV); float FDG = F * D * G; @@ -111,21 +182,39 @@ float4 brdf(Pbr pbr, LightData data) { #if defined(FORWARD_BASE_PASS) if (true) { float remainder = 1.0f; + float2 dfg_uv = float2(data.common.NoV, pbr.roughness); + #if defined(_CLEARCOAT) float cc_f0 = 0.04f; - float Fc = F_Schlick(data.indirect.LoH, cc_f0, f90); - float3 indirect_specular_cc = Fc * data.indirect.specular_cc * pbr.cc_strength; + float Fcc = F_Schlick(data.common.NoV, cc_f0, 1.0f); + float3 indirect_specular_cc = Fcc * data.indirect.specular_cc * pbr.cc_strength; specular += indirect_specular_cc; - remainder -= Fc * pbr.cc_strength; + remainder *= (1.0f - Fcc * pbr.cc_strength); #endif - float F = F_Schlick(data.indirect.LoH, f0, f90); - float3 indirect_specular = F * data.indirect.specular; - specular += indirect_specular; - remainder -= F; +#if defined(_CLOTH_SHEEN) + float DFGcl = _Cloth_Sheen_DFG_LUT.Sample(bilinear_clamp_s, dfg_uv).r; + float3 indirect_specular_cl = DFGcl * data.indirect.specular * pbr.cl_strength * pbr.cl_color; + specular += indirect_specular_cl * remainder; + // Energy conservation for cloth is tricky with IBL. + // A simple approximation is to use the Fresnel of the sheen layer. + float Fcl = F_Schlick(data.common.NoV, 0.04, 1.0); + remainder *= (1.0f - Fcl * pbr.cl_strength); +#endif - float Fd = 1.0f; // Lambertian divide is baked into SH - float3 indirect_diffuse = Fd * remainder * pbr.albedo.xyz * data.indirect.diffuse; + // Standard PBR IBL using split-sum approximation + float2 dfg = _DFG_LUT.Sample(bilinear_clamp_s, dfg_uv).rg; + float3 f0_spec = lerp(0.04f, pbr.albedo.xyz, pbr.metallic); + float3 ibl_specular_reflectance = f0_spec * dfg.x + dfg.y; + float3 indirect_specular = data.indirect.specular * ibl_specular_reflectance; + specular += indirect_specular * remainder; + + // For energy conservation with the diffuse term, we use the view-dependent Fresnel. + float3 F = F_Schlick(data.common.NoV, f0_spec, 1.0f); + remainder *= (1.0f - F); + + // Diffuse is Lambertian, which is pre-integrated into the SH diffuse probe + float3 indirect_diffuse = pbr.albedo.xyz * data.indirect.diffuse * remainder * (1.0 - pbr.metallic); diffuse += indirect_diffuse; } #endif @@ -134,4 +223,3 @@ float4 brdf(Pbr pbr, LightData data) { } #endif // __BRDF_INC - |
