Fix: EEVEE BXDF LUT generation
Fixes: 1. Mixed use of `sample_reflection` and `sample_refraction`. `sample_reflection` is intended for when only reflection is required. For refraction, `sample_vndf` or `sample_refraction` should be used. 2. Wrong weight when accumulating the contribution. Previously `brdf * NV / (fresnel * pdf)` always evaluates to `GL`, but with the new technique of bounded VNDF sampling this is not true anymore. Fixed by adding a field `weight` in the struct `BsdfEval`. 3. Schlick's approximation of the fresnel factor is `F + (1 - F) * (1 - cos(theta))^5`, but BSDF LUT was using `cos^2(theta)`, which was incorrect.
This commit is contained in:
committed by
Clément Foucault
parent
0cf7484817
commit
313a7d6236
@@ -20,6 +20,8 @@ struct BsdfSample {
|
||||
struct BsdfEval {
|
||||
float throughput;
|
||||
float pdf;
|
||||
/* `throughput / pdf`. */
|
||||
float weight;
|
||||
};
|
||||
|
||||
struct ClosureLight {
|
||||
|
||||
@@ -38,12 +38,13 @@ float bxdf_ggx_smith_G1(float NX, float a2)
|
||||
* The Z component can be biased towards 1.
|
||||
* \param Vt: View vector in tangent space.
|
||||
* \param alpha: roughness parameter.
|
||||
* \param do_clamp: clamp for numerical stability during ray tracing.
|
||||
*
|
||||
* \return: the sampled direction and the pdf of sampling the direction.
|
||||
*/
|
||||
BsdfSample bxdf_ggx_sample_reflection(vec3 rand, vec3 Vt, float alpha)
|
||||
BsdfSample bxdf_ggx_sample_reflection(vec3 rand, vec3 Vt, float alpha, const bool do_clamp)
|
||||
{
|
||||
if (alpha < square(BSDF_ROUGHNESS_THRESHOLD)) {
|
||||
if (do_clamp && alpha < square(BSDF_ROUGHNESS_THRESHOLD)) {
|
||||
BsdfSample samp;
|
||||
samp.pdf = 1e6;
|
||||
samp.direction = reflect(-Vt, vec3(0.0, 0.0, 1.0));
|
||||
@@ -92,7 +93,7 @@ BsdfSample bxdf_ggx_sample_reflection(vec3 rand, vec3 Vt, float alpha)
|
||||
|
||||
/* Evaluate the GGX BRDF without the Fresnel term, multiplied by the cosine foreshortening term.
|
||||
* Also evaluate the probability of sampling the reflection direction. */
|
||||
BsdfEval bxdf_ggx_eval_reflection(vec3 N, vec3 L, vec3 V, float alpha)
|
||||
BsdfEval bxdf_ggx_eval_reflection(vec3 N, vec3 L, vec3 V, float alpha, const bool do_clamp)
|
||||
{
|
||||
float NV = dot(N, V);
|
||||
if (NV <= 0.0) {
|
||||
@@ -101,6 +102,7 @@ BsdfEval bxdf_ggx_eval_reflection(vec3 N, vec3 L, vec3 V, float alpha)
|
||||
BsdfEval eval;
|
||||
eval.throughput = 0.0;
|
||||
eval.pdf = 0.0;
|
||||
eval.weight = 0.0;
|
||||
return eval;
|
||||
#endif
|
||||
}
|
||||
@@ -110,11 +112,11 @@ BsdfEval bxdf_ggx_eval_reflection(vec3 N, vec3 L, vec3 V, float alpha)
|
||||
/* This threshold was computed based on precision of NVidia compiler (see #118997).
|
||||
* These drivers tend to produce NaNs in the computation of the NDF (`D`) if alpha is close to 0.
|
||||
*/
|
||||
alpha = max(1e-3, alpha);
|
||||
if (do_clamp) {
|
||||
alpha = max(1e-3, alpha);
|
||||
}
|
||||
|
||||
float a2 = square(alpha);
|
||||
float NH = max(dot(N, H), 1e-8);
|
||||
float D = bxdf_ggx_D(NH, a2);
|
||||
|
||||
BsdfEval eval;
|
||||
|
||||
@@ -124,14 +126,29 @@ BsdfEval bxdf_ggx_eval_reflection(vec3 N, vec3 L, vec3 V, float alpha)
|
||||
float t = sqrt(len_ai_sqr + NV2);
|
||||
if (NV >= 0.0) {
|
||||
float k = (1.0 - a2) * s2 / (s2 + a2 * NV2);
|
||||
eval.pdf = D / (2.0 * (k * NV + t));
|
||||
eval.pdf = 1.0 / (2.0 * (k * NV + t));
|
||||
}
|
||||
else {
|
||||
eval.pdf = D * (t - NV) / (2.0 * len_ai_sqr);
|
||||
eval.pdf = (t - NV) / (2.0 * len_ai_sqr);
|
||||
}
|
||||
|
||||
/* TODO: But also unused for now. */
|
||||
eval.throughput = 1.0;
|
||||
float NH = dot(N, H);
|
||||
float NL = dot(N, L);
|
||||
if (do_clamp) {
|
||||
NH = max(NH, 1e-8);
|
||||
NL = max(NL, 1e-8);
|
||||
}
|
||||
|
||||
float D = bxdf_ggx_D(NH, a2);
|
||||
float G_V = bxdf_ggx_smith_G1(NV, a2);
|
||||
float G_L = bxdf_ggx_smith_G1(NL, a2);
|
||||
|
||||
eval.throughput = (0.25f * G_V * G_L) / NV;
|
||||
eval.weight = eval.throughput / eval.pdf;
|
||||
|
||||
/* Multiply `D` after computing the weighted throughput for numerical stability. */
|
||||
eval.throughput *= D;
|
||||
eval.pdf *= D;
|
||||
|
||||
return eval;
|
||||
}
|
||||
@@ -171,17 +188,19 @@ vec3 bxdf_ggx_sample_vndf(vec3 rand, vec3 Vt, float alpha, out float G_V)
|
||||
* \param Vt: View vector in tangent space.
|
||||
* \param alpha: roughness parameter
|
||||
* \param ior: refractive index of the material.
|
||||
* \param do_clamp: clamp for numerical stability during ray tracing.
|
||||
*
|
||||
* \return: the sampled direction and the pdf of sampling the direction.
|
||||
*/
|
||||
BsdfSample bxdf_ggx_sample_refraction(vec3 rand, vec3 Vt, float alpha, float ior, float thickness)
|
||||
BsdfSample bxdf_ggx_sample_refraction(
|
||||
vec3 rand, vec3 Vt, float alpha, float ior, float thickness, const bool do_clamp)
|
||||
{
|
||||
if (thickness != 0.0) {
|
||||
/* The incoming ray is inside the material for the second refraction event. */
|
||||
ior = 1.0 / ior;
|
||||
}
|
||||
|
||||
if (alpha < square(BSDF_ROUGHNESS_THRESHOLD)) {
|
||||
if (do_clamp && alpha < square(BSDF_ROUGHNESS_THRESHOLD)) {
|
||||
BsdfSample samp;
|
||||
samp.pdf = 1e6;
|
||||
samp.direction = refract(-Vt, vec3(0.0, 0.0, 1.0), 1.0 / ior);
|
||||
@@ -209,18 +228,20 @@ BsdfSample bxdf_ggx_sample_refraction(vec3 rand, vec3 Vt, float alpha, float ior
|
||||
|
||||
/* Evaluate the GGX BTDF without the Fresnel term, multiplied by the cosine foreshortening term.
|
||||
* Also evaluate the probability of sampling the refraction direction. */
|
||||
BsdfEval bxdf_ggx_eval_refraction(vec3 N, vec3 L, vec3 V, float alpha, float ior, float thickness)
|
||||
BsdfEval bxdf_ggx_eval_refraction(
|
||||
vec3 N, vec3 L, vec3 V, float alpha, float ior, float thickness, const bool do_clamp)
|
||||
{
|
||||
if (thickness != 0.0) {
|
||||
ior = 1.0 / ior;
|
||||
}
|
||||
|
||||
float LV = dot(L, V);
|
||||
if (is_equal(ior, 1.0, 1e-4)) {
|
||||
if (do_clamp && is_equal(ior, 1.0, 1e-4)) {
|
||||
/* Only valid when `L` and `V` point in the opposite directions. */
|
||||
BsdfEval eval;
|
||||
eval.throughput = float(is_equal(LV, -1.0, 1e-3));
|
||||
eval.pdf = 1e6;
|
||||
eval.weight = eval.throughput;
|
||||
return eval;
|
||||
}
|
||||
|
||||
@@ -230,6 +251,7 @@ BsdfEval bxdf_ggx_eval_refraction(vec3 N, vec3 L, vec3 V, float alpha, float ior
|
||||
BsdfEval eval;
|
||||
eval.throughput = 0.0;
|
||||
eval.pdf = 0.0;
|
||||
eval.weight = 0.0;
|
||||
return eval;
|
||||
}
|
||||
|
||||
@@ -240,14 +262,18 @@ BsdfEval bxdf_ggx_eval_refraction(vec3 N, vec3 L, vec3 V, float alpha, float ior
|
||||
H *= inv_len_H;
|
||||
|
||||
/* For transmission, `L` lies in the opposite hemisphere as `H`, therefore negate `L`. */
|
||||
float NL = max(dot(N, -L), 1e-8);
|
||||
float NH = max(dot(N, H), 1e-8);
|
||||
float NL = dot(N, -L);
|
||||
float NH = dot(N, H);
|
||||
float NV = dot(N, V);
|
||||
|
||||
/* This threshold was computed based on precision of NVidia compiler (see #118997).
|
||||
* These drivers tend to produce NaNs in the computation of the NDF (`D`) if alpha is close to 0.
|
||||
*/
|
||||
alpha = max(1e-3, alpha);
|
||||
if (do_clamp) {
|
||||
NL = max(NL, 1e-8);
|
||||
NH = max(NH, 1e-8);
|
||||
/* This threshold was computed based on precision of NVidia compiler (see #118997).
|
||||
* These drivers tend to produce NaNs in the computation of the NDF (`D`) if alpha is close to
|
||||
* 0. */
|
||||
alpha = max(1e-3, alpha);
|
||||
}
|
||||
|
||||
float a2 = square(alpha);
|
||||
float G_V = bxdf_ggx_smith_G1(NV, a2);
|
||||
@@ -260,6 +286,7 @@ BsdfEval bxdf_ggx_eval_refraction(vec3 N, vec3 L, vec3 V, float alpha, float ior
|
||||
BsdfEval eval;
|
||||
eval.pdf = (D * G_V * VH * LH * square(ior * inv_len_H)) / NV;
|
||||
eval.throughput = eval.pdf * G_L;
|
||||
eval.weight = G_L;
|
||||
|
||||
return eval;
|
||||
}
|
||||
|
||||
@@ -39,11 +39,13 @@ float closure_evaluate_pdf(ClosureUndetermined cl, vec3 L, vec3 V, float thickne
|
||||
return bxdf_diffuse_eval(cl.N, L).pdf;
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID: {
|
||||
ClosureReflection cl_ = to_closure_reflection(cl);
|
||||
return bxdf_ggx_eval_reflection(cl.N, L, V, square(cl_.roughness)).pdf;
|
||||
float roughness_sq = square(cl_.roughness);
|
||||
return bxdf_ggx_eval_reflection(cl.N, L, V, roughness_sq, true).pdf;
|
||||
}
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
|
||||
ClosureRefraction cl_ = to_closure_refraction(cl);
|
||||
return bxdf_ggx_eval_refraction(cl.N, L, V, square(cl_.roughness), cl_.ior, thickness).pdf;
|
||||
float roughness_sq = square(cl_.roughness);
|
||||
return bxdf_ggx_eval_refraction(cl.N, L, V, roughness_sq, cl_.ior, thickness, true).pdf;
|
||||
}
|
||||
}
|
||||
/* TODO(fclem): Assert. */
|
||||
|
||||
@@ -27,6 +27,7 @@ vec4 ggx_brdf_split_sum(vec3 lut_coord)
|
||||
|
||||
float NV = clamp(1.0 - square(lut_coord.y), 1e-4, 0.9999);
|
||||
vec3 V = vec3(sqrt(1.0 - square(NV)), 0.0, NV);
|
||||
vec3 N = vec3(0.0, 0.0, 1.0);
|
||||
|
||||
/* Integrating BRDF. */
|
||||
float scale = 0.0;
|
||||
@@ -38,15 +39,13 @@ vec4 ggx_brdf_split_sum(vec3 lut_coord)
|
||||
vec3 Xi = sample_cylinder(rand);
|
||||
|
||||
/* Microfacet normal. */
|
||||
vec3 R = bxdf_ggx_sample_reflection(Xi, V, roughness).direction;
|
||||
vec3 H = normalize(V + R);
|
||||
vec3 L = -reflect(V, H);
|
||||
vec3 L = bxdf_ggx_sample_reflection(Xi, V, roughness, false).direction;
|
||||
vec3 H = normalize(V + L);
|
||||
float NL = L.z;
|
||||
|
||||
if (NL > 0.0) {
|
||||
float VH = saturate(dot(V, H));
|
||||
/* Assuming sample visible normals, `weight = brdf * NV / (pdf * fresnel).` */
|
||||
float weight = bxdf_ggx_smith_G1(NL, roughness_sq);
|
||||
float weight = bxdf_ggx_eval_reflection(N, L, V, roughness, false).weight;
|
||||
/* Schlick's Fresnel. */
|
||||
float s = saturate(pow5f(1.0 - VH));
|
||||
scale += (1.0 - s) * weight;
|
||||
@@ -71,7 +70,7 @@ vec4 ggx_brdf_split_sum(vec3 lut_coord)
|
||||
* `transmittance = (1 - F0) * transmission_factor`. */
|
||||
vec4 ggx_bsdf_split_sum(vec3 lut_coord)
|
||||
{
|
||||
float ior = sqrt(lut_coord.x);
|
||||
float ior = clamp(sqrt(lut_coord.x), 1e-4, 0.9999);
|
||||
/* ior is sin of critical angle. */
|
||||
float critical_cos = sqrt(1.0 - saturate(square(ior)));
|
||||
|
||||
@@ -90,6 +89,7 @@ vec4 ggx_bsdf_split_sum(vec3 lut_coord)
|
||||
float roughness_sq = square(roughness);
|
||||
|
||||
vec3 V = vec3(sqrt(1.0 - square(NV)), 0.0, NV);
|
||||
vec3 N = vec3(0.0, 0.0, 1.0);
|
||||
|
||||
/* Integrating BSDF */
|
||||
float scale = 0.0;
|
||||
@@ -100,28 +100,33 @@ vec4 ggx_bsdf_split_sum(vec3 lut_coord)
|
||||
vec2 rand = hammersley_2d(i, sample_count);
|
||||
vec3 Xi = sample_cylinder(rand);
|
||||
|
||||
/* Microfacet normal. */
|
||||
vec3 R = bxdf_ggx_sample_reflection(Xi, V, roughness).direction;
|
||||
vec3 H = normalize(V + R);
|
||||
float HL = 1.0 - (1.0 - square(dot(V, H))) / square(ior);
|
||||
float s = saturate(pow5f(1.0 - saturate(HL)));
|
||||
|
||||
/* Reflection. */
|
||||
vec3 R = bxdf_ggx_sample_reflection(Xi, V, roughness, false).direction;
|
||||
float NR = R.z;
|
||||
if (NR > 0.0) {
|
||||
/* Assuming sample visible normals, `weight = brdf * NV / (pdf * fresnel).` */
|
||||
float weight = bxdf_ggx_smith_G1(NR, roughness_sq);
|
||||
vec3 H = normalize(V + R);
|
||||
vec3 L = refract(-V, H, 1.0 / ior);
|
||||
float HL = abs(dot(H, L));
|
||||
/* Schlick's Fresnel. */
|
||||
float s = saturate(pow5f(1.0 - saturate(HL)));
|
||||
|
||||
float weight = bxdf_ggx_eval_reflection(N, R, V, roughness, false).weight;
|
||||
scale += (1.0 - s) * weight;
|
||||
bias += s * weight;
|
||||
}
|
||||
|
||||
/* Refraction. */
|
||||
vec3 T = refract(-V, H, 1.0 / ior);
|
||||
vec3 T = bxdf_ggx_sample_refraction(Xi, V, roughness, ior, 0.0, false).direction;
|
||||
float NT = T.z;
|
||||
/* In the case of TIR, `T == vec3(0)`. */
|
||||
if (NT < 0.0) {
|
||||
/* Assuming sample visible normals, accumulating `btdf * NV / (pdf * (1 - F0)).` */
|
||||
transmission_factor += (1.0 - s) * bxdf_ggx_smith_G1(NT, roughness_sq);
|
||||
vec3 H = normalize(ior * T + V);
|
||||
float HL = abs(dot(H, T));
|
||||
/* Schlick's Fresnel. */
|
||||
float s = saturate(pow5f(1.0 - saturate(HL)));
|
||||
|
||||
float weight = bxdf_ggx_eval_refraction(N, T, V, roughness, ior, 0.0, false).weight;
|
||||
transmission_factor += (1.0 - s) * weight;
|
||||
}
|
||||
}
|
||||
transmission_factor /= float(sample_count);
|
||||
@@ -141,11 +146,12 @@ vec4 ggx_bsdf_split_sum(vec3 lut_coord)
|
||||
* `transmittance = (1 - F0) * transmission_factor`. */
|
||||
vec4 ggx_btdf_gt_one(vec3 lut_coord)
|
||||
{
|
||||
float f0 = square(lut_coord.x);
|
||||
float inv_ior = (1.0 - f0) / (1.0 + f0);
|
||||
float f0 = clamp(square(lut_coord.x), 1e-4, 0.9999);
|
||||
float ior = (1.0 + f0) / (1.0 - f0);
|
||||
|
||||
float NV = clamp(1.0 - square(lut_coord.y), 1e-4, 0.9999);
|
||||
vec3 V = vec3(sqrt(1.0 - square(NV)), 0.0, NV);
|
||||
vec3 N = vec3(0.0, 0.0, 1.0);
|
||||
|
||||
/* Squaring for perceptually linear roughness, see [Physically Based Shading at Disney]
|
||||
* (https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf)
|
||||
@@ -160,19 +166,18 @@ vec4 ggx_btdf_gt_one(vec3 lut_coord)
|
||||
vec2 rand = hammersley_2d(i, sample_count);
|
||||
vec3 Xi = sample_cylinder(rand);
|
||||
|
||||
/* Microfacet normal. */
|
||||
vec3 R = bxdf_ggx_sample_reflection(Xi, V, roughness).direction;
|
||||
vec3 H = normalize(V + R);
|
||||
|
||||
/* Refraction. */
|
||||
vec3 L = refract(-V, H, inv_ior);
|
||||
vec3 L = bxdf_ggx_sample_refraction(Xi, V, roughness, ior, 0.0, false).direction;
|
||||
float NL = L.z;
|
||||
|
||||
if (NL < 0.0) {
|
||||
vec3 H = normalize(ior * L + V);
|
||||
float HV = abs(dot(H, V));
|
||||
/* Schlick's Fresnel. */
|
||||
float s = saturate(pow5f(1.0 - saturate(dot(V, H))));
|
||||
/* Assuming sample visible normals, accumulating `btdf * NV / (pdf * (1 - F0)).` */
|
||||
transmission_factor += (1.0 - s) * bxdf_ggx_smith_G1(NL, roughness_sq);
|
||||
float s = saturate(pow5f(1.0 - saturate(HV)));
|
||||
|
||||
float weight = bxdf_ggx_eval_refraction(N, L, V, roughness, ior, 0.0, false).weight;
|
||||
transmission_factor += (1.0 - s) * weight;
|
||||
}
|
||||
}
|
||||
transmission_factor /= float(sample_count);
|
||||
|
||||
@@ -51,7 +51,8 @@ BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V, fl
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID: {
|
||||
samp = bxdf_ggx_sample_reflection(random_point_on_cylinder,
|
||||
V * tangent_to_world,
|
||||
square(to_closure_reflection(cl).roughness));
|
||||
square(to_closure_reflection(cl).roughness),
|
||||
true);
|
||||
break;
|
||||
}
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
|
||||
@@ -59,7 +60,8 @@ BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V, fl
|
||||
V * tangent_to_world,
|
||||
square(to_closure_refraction(cl).roughness),
|
||||
to_closure_refraction(cl).ior,
|
||||
thickness);
|
||||
thickness,
|
||||
true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user