diff options
Diffstat (limited to 'shaders/include/brdf.h')
| -rw-r--r-- | shaders/include/brdf.h | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/shaders/include/brdf.h b/shaders/include/brdf.h new file mode 100644 index 0000000..cafe289 --- /dev/null +++ b/shaders/include/brdf.h @@ -0,0 +1,99 @@ +// Specifies minimal reflectance for dielectrics (when metalness is zero)
+// Nothing has lower reflectance than 2%, but we use 4% to have consistent results with UE4, Frostbite, et al.
+#define MIN_DIELECTRICS_F0 0.04f
+
+const uint DIFFUSE = 1;
+const uint SPECULAR = 2;
+
+// Samples a microfacet normal for the GGX distribution using VNDF method.
+// Source: "Sampling the GGX Distribution of Visible Normals" by Heitz
+// See also https://hal.inria.fr/hal-00996995v1/document and http://jcgt.org/published/0007/04/01/
+// Random variables 'u' must be in <0;1) interval
+// PDF is 'G1(NdotV) * D'
+vec3 sample_GGXVNDF(vec3 Ve, vec2 alpha2D, uint seed) {
+ // Section 3.2: transforming the view direction to the hemisphere configuration
+ vec3 Vh = normalize(vec3(alpha2D.x * Ve.x, alpha2D.y * Ve.y, Ve.z));
+
+ // Section 4.1: orthonormal basis (with special case if cross product is zero)
+ float lensq = Vh.x * Vh.x + Vh.y * Vh.y;
+ vec3 T1 = lensq > 0.0f ? vec3(-Vh.y, Vh.x, 0.0f) * inversesqrt(lensq) : vec3(1.0f, 0.0f, 0.0f);
+ vec3 T2 = cross(Vh, T1);
+
+ // Section 4.2: parameterization of the projected area
+ float r = sqrt(rand(seed));
+ float phi = TWO_PI * rand(seed);
+ float t1 = r * cos(phi);
+ float t2 = r * sin(phi);
+ float s = 0.5f * (1.0f + Vh.z);
+ t2 = mix(sqrt(1.0f - t1 * t1), t2, s);
+
+ // Section 4.3: reprojection onto hemisphere
+ vec3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.0f, 1.0f - t1 * t1 - t2 * t2)) * Vh;
+
+ // Section 3.4: transforming the normal back to the ellipsoid configuration
+ return normalize(vec3(alpha2D.x * Nh.x, alpha2D.y * Nh.y, max(0.0f, Nh.z)));
+}
+
+// Smith G1 term (masking function) further optimized for GGX distribution (by substituting G_a into G1_GGX)
+float Smith_G1_GGX(float alpha, float NdotS, float alpha_squared, float NdotS_squared) {
+ return 2.0f / (sqrt(((alpha_squared * (1.0f - NdotS_squared)) + NdotS_squared) / NdotS_squared) + 1.0f);
+}
+
+// Weight for the reflection ray sampled from GGX distribution using VNDF method
+float specular_sample_weight_GGXVNDF(float alpha, float alpha_squared, float NdotL, float NdotV, float HdotL, float NdotH) {
+#if USE_HEIGHT_CORRELATED_G2
+ return Smith_G2_Over_G1_Height_Correlated(alpha, alpha_squared, NdotL, NdotV);
+#else
+ return Smith_G1_GGX(alpha, NdotL, alpha_squared, NdotL * NdotL);
+#endif
+}
+
+vec3 base_color_to_specular_F0(vec3 base_color, float metalness) {
+ return mix(vec3(MIN_DIELECTRICS_F0), base_color, metalness);
+}
+
+vec3 base_color_to_diffuse_reflectance(vec3 base_color, float metalness) {
+ return base_color * (1.0f - metalness);
+}
+
+// Schlick's approximation to Fresnel term
+// f90 should be 1.0, except for the trick used by Schuler (see 'shadowed_F90' function)
+vec3 eval_fresnel_schlick(vec3 f0, float f90, float NdotS) {
+ return f0 + (f90 - f0) * pow(1.0f - NdotS, 5.0f);
+}
+
+vec3 eval_fresnel(vec3 f0, float f90, float NdotS) {
+ // Default is Schlick's approximation
+ return eval_fresnel_schlick(f0, f90, NdotS);
+}
+
+// Attenuates F90 for very low F0 values
+// Source: "An efficient and Physically Plausible Real-Time Shading Model" in ShaderX7 by Schuler
+// Also see section "Overbright highlights" in Hoffman's 2010 "Crafting Physically Motivated Shading Models for Game Development" for discussion
+// IMPORTANT: Note that when F0 is calculated using metalness, it's value is never less than MIN_DIELECTRICS_F0, and therefore,
+// this adjustment has no effect. To be effective, F0 must be authored separately, or calculated in different way. See main text for discussion.
+float shadowed_F90(vec3 F0) {
+ // This scaler value is somewhat arbitrary, Schuler used 60 in his article. In here, we derive it from MIN_DIELECTRICS_F0 so
+ // that it takes effect for any reflectance lower than least reflective dielectrics
+ //const float t = 60.0f;
+ const float t = (1.f / MIN_DIELECTRICS_F0);
+ return min(1.f, t * luminance(F0));
+}
+
+float get_brdf_probability(vec3 color, float metalness, vec3 view, vec3 shading_normal) {
+ // Evaluate Fresnel term using the shading normal
+ // Note: we use the shading normal instead of the microfacet normal (half-vector) for Fresnel term here. That's suboptimal for rough surfaces at grazing angles, but half-vector is yet unknown at this point
+ vec3 specular_F0 = vec3(luminance(base_color_to_specular_F0(color, metalness)));
+ float diffuse_reflectance = luminance(base_color_to_diffuse_reflectance(color, metalness));
+ float fresnel = clamp(luminance(eval_fresnel(specular_F0, shadowed_F90(specular_F0), max(0.0f, dot(view, shading_normal)))), 0.0, 1.0);
+
+ // Approximate relative contribution of BRDFs using the Fresnel term
+ float specular = fresnel;
+ float diffuse = diffuse_reflectance * (1.0f - fresnel); //< If diffuse term is weighted by Fresnel, apply it here as well
+
+ // Return probability of selecting specular BRDF over diffuse BRDF
+ float p = (specular / max(0.0001f, (specular + diffuse)));
+
+ // Clamp probability to avoid undersampling of less prominent BRDF
+ return clamp(p, 0.1f, 0.9f);
+}
|
