Files
filament/shaders/src/brdf.fs
Mathias Agopian 036bfa9b20 Materials now have a quality property.
We used to have that before, but it was hardcoded and depended on
mobile vs. desktop.
With this change, users can set the quality on the material itself.
The default match the current settings. There are 3 quality levels:

low: enables optimization that might not be 100% correct 
     (e.g. abs(x) instead of sqrt(x*x))

normal: like low, but doesn't sacrifices correctness.

high: could be improved upsampling, etc...
2021-04-27 14:20:50 -07:00

255 lines
9.0 KiB
GLSL

//------------------------------------------------------------------------------
// 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_LAMBERT
#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 vec3 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
#if defined(TARGET_MOBILE)
vec3 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;
highp vec3 d = vec3(ab * ToH, at * BoH, a2 * NoH);
highp 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 / mix(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(vec3(at * ToV, ab * BoV, NoV));
float lambdaL = NoV * length(vec3(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)));
}
vec3 F_Schlick(const vec3 f0, float f90, float VoH) {
// Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"
return f0 + (f90 - f0) * pow5(1.0 - VoH);
}
vec3 F_Schlick(const vec3 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 vec3 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
}
vec3 fresnel(const vec3 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, vec3(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 vec3 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
}