#ifndef __BRDF_INC #define __BRDF_INC #include "pbr.cginc" #include "lighting.cginc" #include "lysenko.cginc" #include "math.cginc" // Schlick "An Inexpensive BRDF Model for Physically-based Rendering". // Equation 24. // f0: Reflectance at normal incidence. Typically around 0.04. // f90: Reflectance at grazing incidence. Typically around 1.0. float F_Schlick(float LoH, float f0, float f90) { float term = 1.0f - LoH; float term2 = term * term; float term5 = term2 * term2 * term; return f0 + (f90 - f0) * term5; } // Walter "Microfacet Models for Refraction through Rough Surfaces" // Equation 33. // In the paper: // - m = microsurface normal // - n = macrosurface normal // - theta_m = angle between micro- & macrosurface normals // - alpha = roughness // - cos(theta_m) = NoH // Per sohcahtoa: // tan(theta) = sin(theta) / cos(theta) // tan^2(theta) = sin^2(theta) / cos^2(theta) // = (1 - cos^2(theta)) / cos^2(theta) // = -1 + 1 / cos^2(theta) float D_GGX(float roughness, float NoH) { float r2 = roughness * roughness; float NoH2 = NoH * NoH; float NoH4 = NoH2 * NoH2; float k = rcp(NoH2) - 1; float r2_plus_k = r2 + k; // Not sure why, but not using the factor of PI here makes the specular match // the Unity standard much more closely. Maybe the author was just folding // the 4.0 (historically used to be a PI) into the GGX calculation? float denom = NoH4 * r2_plus_k * r2_plus_k; return r2 / denom; } // Hammon "PBR Diffuse Lighting for GGX+Smith Microsurfaces" // Slide 84. Note that we remove the (4 * NoL * NoV) from the // 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 denom = 2.0f * lerp(2.0f * NoL * NoV, NoL + NoV, roughness); return rcp(denom); } float4 brdf(Pbr pbr, LightData data) { float3 specular = 0; float3 diffuse = 0; float f0 = 0.04f; const float f90 = 1.0f; //#define FURNACE_TEST_DIRECT #if defined(FURNACE_TEST_DIRECT) // Create the conditions for the standard BRDF furnace test. // Only applies to the direct lighting stage. The only variable left over is // NoV. f0 = 1; data.direct.color = 1; data.direct.NoL = 1; data.direct.NoH = 1; data.direct.LoH = 1; #endif // Direct if (true) { float remainder = 1.0f; #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; direct_specular_cc = max(0, direct_specular_cc); specular += direct_specular_cc; remainder -= Fc * pbr.cc_strength; #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 FDG = F * D * G; float3 direct_specular = FDG * remainder * data.direct.color * data.direct.NoL * lerp(1.0f, pbr.albedo.xyz, pbr.metallic); direct_specular = max(0, direct_specular); specular += direct_specular; remainder -= F; float Fd = Fd_OrenNayar(pbr.roughness, data.common.NoV, data.direct.NoL, data.direct.LoV) / PI; float3 direct_diffuse = Fd * remainder * (1.0f - pbr.metallic) * pbr.albedo.xyz * data.direct.color; direct_diffuse = max(0, direct_diffuse); diffuse += direct_diffuse; } // Indirect if (true) { float remainder = 1.0f; #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; specular += indirect_specular_cc; remainder -= Fc * 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; float Fd = 1.0f; // Lambertian divide is baked into SH float3 indirect_diffuse = Fd * remainder * pbr.albedo.xyz * data.indirect.diffuse; diffuse += indirect_diffuse; } return float4(diffuse + specular, 1); } #endif // __BRDF_INC