diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl index 8f876b348bb..0f32f39c341 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_lib.glsl @@ -20,6 +20,8 @@ struct BsdfSample { struct BsdfEval { float throughput; float pdf; + /* `throughput / pdf`. */ + float weight; }; struct ClosureLight { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_microfacet_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_microfacet_lib.glsl index 9481675e2ee..b2f6094256f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_microfacet_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_bxdf_microfacet_lib.glsl @@ -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; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_closure_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_closure_lib.glsl index 44eaf8578af..0068f50813b 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_closure_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_closure_lib.glsl @@ -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. */ diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_lut_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_lut_comp.glsl index 834d40f9712..6cfd7ba9dba 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_lut_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_lut_comp.glsl @@ -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); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl index 6649f4c00af..c508c2ece53 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_ray_generate_lib.glsl @@ -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; } }