Fix: EEVEE: Incorrect GGX BRDF pdf and BTDF evaluation #111591

Merged
Clément Foucault merged 4 commits from weizhen/blender:fix-eevee-bsdf into main 2023-08-27 20:51:41 +02:00
5 changed files with 66 additions and 43 deletions

View File

@ -1036,15 +1036,15 @@ void EEVEE_lightbake_filter_glossy(EEVEE_ViewLayerData *sldata,
break;
case 2:
pinfo->samples_len = 40.0f;
bias = 2.0f;
bias = 1.0f;
break;
case 3:
pinfo->samples_len = 64.0f;
bias = 2.0f;
bias = 1.0f;
break;
default:
pinfo->samples_len = 128.0f;
bias = 2.0f;
bias = 1.0f;
break;
}
#else /* Constant Sample count (slow) */

View File

@ -99,12 +99,22 @@ vec3 F_brdf_multi_scatter(vec3 f0, vec3 f90, vec2 lut)
}
/* GGX */
float bxdf_ggx_D(float NH, float a2)
{
return a2 / (M_PI * sqr((a2 - 1.0) * (NH * NH) + 1.0));
}
float D_ggx_opti(float NH, float a2)
{
float tmp = (NH * a2 - NH) * NH + 1.0;
return M_PI * tmp * tmp; /* Doing RCP and mul a2 at the end */
}
float bxdf_ggx_smith_G1(float NX, float a2)
{
return 2.0 / (1.0 + sqrt(1.0 + a2 * (1.0 / (NX * NX) - 1.0)));
}
float G1_Smith_GGX_opti(float NX, float a2)
{
/* Using Brian Karis approach and refactoring by NX/NX
@ -115,6 +125,7 @@ float G1_Smith_GGX_opti(float NX, float a2)
/* return 2 / (1 + sqrt(1 + a2 * (1 - NX*NX) / (NX*NX) ) ); /* Reference function */
}
/* Compute the GGX BRDF without the Fresnel term, multiplied by the cosine foreshortening term. */
float bsdf_ggx(vec3 N, vec3 L, vec3 V, float roughness)
{
float a = roughness;
@ -125,12 +136,11 @@ float bsdf_ggx(vec3 N, vec3 L, vec3 V, float roughness)
float NL = max(dot(N, L), 1e-8);
float NV = max(dot(N, V), 1e-8);
float G = G1_Smith_GGX_opti(NV, a2) * G1_Smith_GGX_opti(NL, a2); /* Doing RCP at the end */
float D = D_ggx_opti(NH, a2);
float G = bxdf_ggx_smith_G1(NV, a2) * bxdf_ggx_smith_G1(NL, a2);
float D = bxdf_ggx_D(NH, a2);
/* Denominator is canceled by G1_Smith */
/* bsdf = D * G / (4.0 * NL * NV); /* Reference function */
return NL * a2 / (D * G); /* NL to Fit cycles Equation : line. 345 in bsdf_microfacet.h */
/* brdf * NL = `((D * G) / (4 * NV * NL)) * NL`. */
return (0.25 * D * G) / NV;
}
void accumulate_light(vec3 light, float fac, inout vec4 accum)

View File

@ -19,9 +19,9 @@ float pdf_ggx_reflect(float NH, float NV, float VH, float alpha)
{
float a2 = sqr(alpha);
#if USE_VISIBLE_NORMAL
float D = a2 / D_ggx_opti(NH, a2);
float G1 = NV * 2.0 / G1_Smith_GGX_opti(NV, a2);
return G1 * VH * D / NV;
float D = bxdf_ggx_D(NH, a2);
float G1 = bxdf_ggx_smith_G1(NV, a2);
return 0.25 * D * G1 / NV;
#else
return NH * a2 / D_ggx_opti(NH, a2);
#endif

View File

@ -6,7 +6,7 @@
* BxDF evaluation functions.
*/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
/* -------------------------------------------------------------------- */
/** \name GGX
@ -15,12 +15,7 @@
float bxdf_ggx_D(float NH, float a2)
{
return a2 / (M_PI * sqr((NH * a2 - NH) * NH + 1.0));
}
float bxdf_ggx_smith_G1(float NX, float a2)
{
return 2.0 / (1.0 + sqrt(1.0 + a2 * (1 - NX * NX) / (NX * NX)));
return a2 / (M_PI * square((a2 - 1.0) * (NH * NH) + 1.0));
}
float bxdf_ggx_D_opti(float NH, float a2)
@ -30,53 +25,71 @@ float bxdf_ggx_D_opti(float NH, float a2)
return M_PI * tmp * tmp;
}
float bxdf_ggx_smith_G1(float NX, float a2)
{
return 2.0 / (1.0 + sqrt(1.0 + a2 * (1.0 / (NX * NX) - 1.0)));
}
float bxdf_ggx_smith_G1_opti(float NX, float a2)
{
/* Using Brian Karis approach and refactoring by NX/NX
* this way the `(2*NL)*(2*NV)` in `G = G1(V) * G1(L)` gets canceled by the BRDF denominator
* `4*NL*NV` Rcp is done on the whole G later. Note that this is not convenient for the
* transmission formula. */
// return 2 / (1 + sqrt(1 + a2 * (1 - NX*NX) / (NX*NX) ) ); /* Reference function. */
return NX + sqrt(NX * (NX - NX * a2) + a2);
}
/* Compute the GGX BRDF without the Fresnel term, multiplied by the cosine foreshortening term. */
float bsdf_ggx(vec3 N, vec3 L, vec3 V, float roughness)
{
float a2 = sqr(roughness);
float a2 = square(roughness);
vec3 H = normalize(L + V);
float NH = max(dot(N, H), 1e-8);
float NL = max(dot(N, L), 1e-8);
float NV = max(dot(N, V), 1e-8);
/* Doing RCP at the end */
float G = bxdf_ggx_smith_G1_opti(NV, a2) * bxdf_ggx_smith_G1_opti(NL, a2);
float D = bxdf_ggx_D_opti(NH, a2);
/* TODO: maybe implement non-separable shadowing-masking term following Cycles. */
float G = bxdf_ggx_smith_G1(NV, a2) * bxdf_ggx_smith_G1(NL, a2);
float D = bxdf_ggx_D(NH, a2);
/* Denominator is canceled by G1_Smith */
// bsdf = D * G / (4.0 * NL * NV); /* Reference function. */
/* NL term to fit Cycles. NOTE(fclem): Not sure what it */
return NL * a2 / (D * G);
/* brdf * NL = `((D * G) / (4 * NV * NL)) * NL`. */
return (0.25 * D * G) / NV;
}
/* Compute the GGX BTDF without the Fresnel term, multiplied by the cosine foreshortening term. */
float btdf_ggx(vec3 N, vec3 L, vec3 V, float roughness, float eta)
{
float a2 = sqr(roughness);
float LV = dot(L, V);
if (is_equal(eta, 1.0, 1e-4)) {
/* Only valid when `L` and `V` point in the opposite directions. */
return float(is_equal(LV, -1.0, 1e-3));
}
vec3 H = normalize(L + V);
bool valid = (eta < 1.0) ? (LV < -eta) : (LV * eta < -1.0);
if (!valid) {
/* Impossible configuration for transmission due to total internal reflection. */
return 0.0;
}
vec3 H = eta * L + V;
H = (eta < 1.0) ? H : -H;
float inv_len_H = safe_rcp(length(H));
H *= inv_len_H;
/* For transmission, `L` lies in the opposite hemisphere as `H`, therefore negate `L`. */
float NH = max(dot(N, H), 1e-8);
float NL = max(dot(N, L), 1e-8);
float NL = max(dot(N, -L), 1e-8);
float NV = max(dot(N, V), 1e-8);
float VH = max(dot(V, H), 1e-8);
float LH = max(dot(L, H), 1e-8);
float Ht2 = sqr(eta * LH + VH);
float VH = saturate(dot(V, H));
float LH = saturate(dot(-L, H));
/* Doing RCP at the end */
float G = bxdf_ggx_smith_G1_opti(NV, a2) * bxdf_ggx_smith_G1_opti(NL, a2);
float D = bxdf_ggx_D_opti(NH, a2);
float a2 = square(roughness);
float G = bxdf_ggx_smith_G1(NV, a2) * bxdf_ggx_smith_G1(NL, a2);
float D = bxdf_ggx_D(NH, a2);
/* `btdf = abs(VH*LH) * ior^2 * D * G(V) * G(L) / (Ht2 * NV)`. */
return abs(VH * LH) * sqr(eta) * 4.0 * a2 / (D * G * (Ht2 * NV));
/* `btdf * NL = abs(VH * LH) * ior^2 * D * G(V) * G(L) / (Ht2 * NV * NL) * NL`. */
return (D * G * VH * LH * square(eta * inv_len_H)) / NV;
}
/** \} */

View File

@ -19,7 +19,7 @@ float sample_pdf_ggx_reflect(float NH, float NV, float VH, float G1, float alpha
{
float a2 = sqr(alpha);
#if GGX_USE_VISIBLE_NORMAL
return G1 * VH * bxdf_ggx_D(NH, a2) / NV;
return 0.25 * bxdf_ggx_D(NH, a2) * G1 / NV;
#else
return NH * bxdf_ggx_D(NH, a2);
#endif
@ -29,9 +29,9 @@ float sample_pdf_ggx_refract(
float NH, float NV, float VH, float LH, float G1, float alpha, float eta)
{
float a2 = sqr(alpha);
float D = bxdf_ggx_D_opti(NH, a2);
float D = bxdf_ggx_D(NH, a2);
float Ht2 = sqr(eta * LH + VH);
return VH * abs(LH) * ((G1 * D) * sqr(eta) * a2 / (D * NV * Ht2));
return (D * G1 * abs(VH * LH) * sqr(eta)) / (NV * Ht2);
}
/**
@ -89,7 +89,7 @@ vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt, out float G1)
* \param T: Tangent vector.
* \param B: Bitangent vector.
* \return pdf: associated pdf for a reflection ray. 0 if ray is invalid.
* \return pdf: the pdf of sampling the reflected ray. 0 if ray is invalid.
*/
vec3 sample_ggx_reflect(vec3 rand, float alpha, vec3 V, vec3 N, vec3 T, vec3 B, out float pdf)
{
@ -120,7 +120,7 @@ vec3 sample_ggx_reflect(vec3 rand, float alpha, vec3 V, vec3 N, vec3 T, vec3 B,
* \param T: Tangent vector.
* \param B: Bitangent vector.
* \return pdf_reflect: associated pdf for a reflection ray.
* \return pdf: the pdf of sampling the refracted ray. 0 if ray is invalid.
*/
vec3 sample_ggx_refract(
vec3 rand, float alpha, float ior, vec3 V, vec3 N, vec3 T, vec3 B, out float pdf)