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...
255 lines
9.0 KiB
GLSL
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
|
|
}
|