#ifndef FILAMENT_BRDF_INCLUDED #define FILAMENT_BRDF_INCLUDED //------------------------------------------------------------------------------ // BRDF configuration //------------------------------------------------------------------------------ // Diffuse BRDFs #define DIFFUSE_LAMBERT 0 #define DIFFUSE_BURLEY 1 // Specular BRDF // Normal distribution functions #define SPECULAR_D_GGX 0 // Anisotropic NDFs #define SPECULAR_D_GGX_ANISOTROPIC 0 // Cloth NDFs #define SPECULAR_D_CHARLIE 0 // Visibility functions #define SPECULAR_V_SMITH_GGX 0 #define SPECULAR_V_SMITH_GGX_FAST 1 #define SPECULAR_V_GGX_ANISOTROPIC 2 #define SPECULAR_V_KELEMEN 3 #define SPECULAR_V_NEUBELT 4 // Fresnel functions #define SPECULAR_F_SCHLICK 0 #define BRDF_DIFFUSE DIFFUSE_BURLEY #if FILAMENT_QUALITY < FILAMENT_QUALITY_HIGH #define BRDF_SPECULAR_D SPECULAR_D_GGX #define BRDF_SPECULAR_V SPECULAR_V_SMITH_GGX_FAST #define BRDF_SPECULAR_F SPECULAR_F_SCHLICK #else #define BRDF_SPECULAR_D SPECULAR_D_GGX #define BRDF_SPECULAR_V SPECULAR_V_SMITH_GGX #define BRDF_SPECULAR_F SPECULAR_F_SCHLICK #endif #define BRDF_CLEAR_COAT_D SPECULAR_D_GGX #define BRDF_CLEAR_COAT_V SPECULAR_V_KELEMEN #define BRDF_ANISOTROPIC_D SPECULAR_D_GGX_ANISOTROPIC #define BRDF_ANISOTROPIC_V SPECULAR_V_GGX_ANISOTROPIC #define BRDF_CLOTH_D SPECULAR_D_CHARLIE #define BRDF_CLOTH_V SPECULAR_V_NEUBELT //------------------------------------------------------------------------------ // Specular BRDF implementations //------------------------------------------------------------------------------ float D_GGX(float roughness, float NoH, const float3 h) { // Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces" // In mediump, there are two problems computing 1.0 - NoH^2 // 1) 1.0 - NoH^2 suffers floating point cancellation when NoH^2 is close to 1 (highlights) // 2) NoH doesn't have enough precision around 1.0 // Both problem can be fixed by computing 1-NoH^2 in highp and providing NoH in highp as well // However, we can do better using Lagrange's identity: // ||a x b||^2 = ||a||^2 ||b||^2 - (a . b)^2 // since N and H are unit vectors: ||N x H||^2 = 1.0 - NoH^2 // This computes 1.0 - NoH^2 directly (which is close to zero in the highlights and has // enough precision). // Overall this yields better performance, keeping all computations in mediump // Not available without reworking to pass NxH to the function #if defined(TARGET_MOBILE) && false float3 NxH = cross(shading.normal, h); float oneMinusNoHSquared = dot(NxH, NxH); #else float oneMinusNoHSquared = 1.0 - NoH * NoH; #endif float a = NoH * roughness; float k = roughness / (oneMinusNoHSquared + a * a); float d = k * k * (1.0 / PI); return saturateMediump(d); } float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) { // Burley 2012, "Physically-Based Shading at Disney" // The values at and ab are perceptualRoughness^2, a2 is therefore perceptualRoughness^4 // The dot product below computes perceptualRoughness^8. We cannot fit in fp16 without clamping // the roughness to too high values so we perform the dot product and the division in fp32 float a2 = at * ab; float3 d = float3(ab * ToH, at * BoH, a2 * NoH); float d2 = dot(d, d); float b2 = a2 / d2; return a2 * b2 * b2 * (1.0 / PI); } float D_Charlie(float roughness, float NoH) { // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF" float invAlpha = 1.0 / roughness; float cos2h = NoH * NoH; float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16 return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI); } float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) { // Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" float a2 = roughness * roughness; // TODO: lambdaV can be pre-computed for all the lights, it should be moved out of this function float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); float v = 0.5 / (lambdaV + lambdaL); // a2=0 => v = 1 / 4*NoL*NoV => min=1/4, max=+inf // a2=1 => v = 1 / 2*(NoL+NoV) => min=1/4, max=+inf // clamp to the maximum value representable in mediump return saturateMediump(v); } float V_SmithGGXCorrelated_Fast(float roughness, float NoV, float NoL) { // Hammon 2017, "PBR Diffuse Lighting for GGX+Smith Microsurfaces" float v = 0.5 / lerp(2.0 * NoL * NoV, NoL + NoV, roughness); return saturateMediump(v); } float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float ToV, float BoV, float ToL, float BoL, float NoV, float NoL) { // Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" // TODO: lambdaV can be pre-computed for all the lights, it should be moved out of this function float lambdaV = NoL * length(float3(at * ToV, ab * BoV, NoV)); float lambdaL = NoV * length(float3(at * ToL, ab * BoL, NoL)); float v = 0.5 / (lambdaV + lambdaL); return saturateMediump(v); } float V_Kelemen(float LoH) { // Kelemen 2001, "A Microfacet Based Coupled Specular-Matte BRDF Model with Importance Sampling" return saturateMediump(0.25 / (LoH * LoH)); } float V_Neubelt(float NoV, float NoL) { // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" return saturateMediump(1.0 / (4.0 * (NoL + NoV - NoL * NoV))); } float3 F_Schlick(const float3 f0, float f90, float VoH) { // Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering" return f0 + (f90 - f0) * pow5(1.0 - VoH); } float3 F_Schlick(const float3 f0, float VoH) { float f = pow(1.0 - VoH, 5.0); return f + f0 * (1.0 - f); } float F_Schlick(float f0, float f90, float VoH) { return f0 + (f90 - f0) * pow5(1.0 - VoH); } //------------------------------------------------------------------------------ // Specular BRDF dispatch //------------------------------------------------------------------------------ float distribution(float roughness, float NoH, const float3 h) { #if BRDF_SPECULAR_D == SPECULAR_D_GGX return D_GGX(roughness, NoH, h); #endif } float visibility(float roughness, float NoV, float NoL) { #if BRDF_SPECULAR_V == SPECULAR_V_SMITH_GGX return V_SmithGGXCorrelated(roughness, NoV, NoL); #elif BRDF_SPECULAR_V == SPECULAR_V_SMITH_GGX_FAST return V_SmithGGXCorrelated_Fast(roughness, NoV, NoL); #endif } float3 fresnel(const float3 f0, float LoH) { #if BRDF_SPECULAR_F == SPECULAR_F_SCHLICK #if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW return F_Schlick(f0, LoH); // f90 = 1.0 #else float f90 = saturate(dot(f0, (50.0 * 0.33))); return F_Schlick(f0, f90, LoH); #endif #endif } float distributionAnisotropic(float at, float ab, float ToH, float BoH, float NoH) { #if BRDF_ANISOTROPIC_D == SPECULAR_D_GGX_ANISOTROPIC return D_GGX_Anisotropic(at, ab, ToH, BoH, NoH); #endif } float visibilityAnisotropic(float roughness, float at, float ab, float ToV, float BoV, float ToL, float BoL, float NoV, float NoL) { #if BRDF_ANISOTROPIC_V == SPECULAR_V_SMITH_GGX return V_SmithGGXCorrelated(roughness, NoV, NoL); #elif BRDF_ANISOTROPIC_V == SPECULAR_V_GGX_ANISOTROPIC return V_SmithGGXCorrelated_Anisotropic(at, ab, ToV, BoV, ToL, BoL, NoV, NoL); #endif } float distributionClearCoat(float roughness, float NoH, const float3 h) { #if BRDF_CLEAR_COAT_D == SPECULAR_D_GGX return D_GGX(roughness, NoH, h); #endif } float visibilityClearCoat(float LoH) { #if BRDF_CLEAR_COAT_V == SPECULAR_V_KELEMEN return V_Kelemen(LoH); #endif } float distributionCloth(float roughness, float NoH) { #if BRDF_CLOTH_D == SPECULAR_D_CHARLIE return D_Charlie(roughness, NoH); #endif } float visibilityCloth(float NoV, float NoL) { #if BRDF_CLOTH_V == SPECULAR_V_NEUBELT return V_Neubelt(NoV, NoL); #endif } //------------------------------------------------------------------------------ // Diffuse BRDF implementations //------------------------------------------------------------------------------ float Fd_Lambert() { return 1.0 / PI; } float Fd_Burley(float roughness, float NoV, float NoL, float LoH) { // Burley 2012, "Physically-Based Shading at Disney" float f90 = 0.5 + 2.0 * roughness * LoH * LoH; float lightScatter = F_Schlick(1.0, f90, NoL); float viewScatter = F_Schlick(1.0, f90, NoV); return lightScatter * viewScatter * (1.0 / PI); } // Energy conserving wrap diffuse term, does *not* include the divide by pi float Fd_Wrap(float NoL, float w) { return saturate((NoL + w) / sq(1.0 + w)); } //------------------------------------------------------------------------------ // Diffuse BRDF dispatch //------------------------------------------------------------------------------ float diffuse(float roughness, float NoV, float NoL, float LoH) { #if BRDF_DIFFUSE == DIFFUSE_LAMBERT return Fd_Lambert(); #elif BRDF_DIFFUSE == DIFFUSE_BURLEY return Fd_Burley(roughness, NoV, NoL, LoH); #endif } #endif // FILAMENT_BRDF_INCLUDED