EEVEE-Next: Use PDF instead of BSDF for denoising
This adds PDF computation for eval functions as well as using PDF for spatial denoising. This seems to not do have any impact but at least it seems more sounds than bsdf / pdf. Also computing pdf is cheaper than whole bsdf. Pull Request: https://projects.blender.org/blender/blender/pulls/121858
This commit is contained in:
committed by
Clément Foucault
parent
040bfcee12
commit
d4b4107d32
@@ -42,7 +42,7 @@ float bxdf_ggx_smith_G1_opti(float NX, float a2)
|
||||
}
|
||||
|
||||
/* Compute the GGX BRDF without the Fresnel term, multiplied by the cosine foreshortening term. */
|
||||
float bsdf_ggx_reflect(vec3 N, vec3 L, vec3 V, float roughness)
|
||||
float bsdf_ggx_reflect(vec3 N, vec3 L, vec3 V, float roughness, out float pdf)
|
||||
{
|
||||
float a2 = square(roughness);
|
||||
|
||||
@@ -52,18 +52,21 @@ float bsdf_ggx_reflect(vec3 N, vec3 L, vec3 V, float roughness)
|
||||
float NV = max(dot(N, V), 1e-8);
|
||||
|
||||
/* 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 G_V = bxdf_ggx_smith_G1(NV, a2);
|
||||
float G_L = bxdf_ggx_smith_G1(NL, a2);
|
||||
float D = bxdf_ggx_D(NH, a2);
|
||||
|
||||
pdf = D / ((1.0 + G_V) * 4.0 * NV);
|
||||
/* BRDF * NL = `((D * G) / (4 * NV * NL)) * NL`. */
|
||||
return (0.25 * D * G) / NV;
|
||||
return (D * (G_V * G_L)) / (4.0 * NV);
|
||||
}
|
||||
|
||||
/* Compute the GGX BTDF without the Fresnel term, multiplied by the cosine foreshortening term. */
|
||||
float bsdf_ggx_refract(vec3 N, vec3 L, vec3 V, float roughness, float eta)
|
||||
float bsdf_ggx_refract(vec3 N, vec3 L, vec3 V, float roughness, float eta, out float pdf)
|
||||
{
|
||||
float LV = dot(L, V);
|
||||
if (is_equal(eta, 1.0, 1e-4)) {
|
||||
pdf = 1.0;
|
||||
/* Only valid when `L` and `V` point in the opposite directions. */
|
||||
return float(is_equal(LV, -1.0, 1e-3));
|
||||
}
|
||||
@@ -71,6 +74,7 @@ float bsdf_ggx_refract(vec3 N, vec3 L, vec3 V, float roughness, float eta)
|
||||
bool valid = (eta < 1.0) ? (LV < -eta) : (LV * eta < -1.0);
|
||||
if (!valid) {
|
||||
/* Impossible configuration for transmission due to total internal reflection. */
|
||||
pdf = 0.0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -87,11 +91,14 @@ float bsdf_ggx_refract(vec3 N, vec3 L, vec3 V, float roughness, float eta)
|
||||
float LH = saturate(dot(-L, H));
|
||||
|
||||
float a2 = square(roughness);
|
||||
float G = bxdf_ggx_smith_G1(NV, a2) * bxdf_ggx_smith_G1(NL, a2);
|
||||
float G_V = bxdf_ggx_smith_G1(NV, a2);
|
||||
float G_L = bxdf_ggx_smith_G1(NL, a2);
|
||||
float D = bxdf_ggx_D(NH, a2);
|
||||
float common = D * VH * LH * square(eta * inv_len_H);
|
||||
|
||||
pdf = common / ((1.0 + G_V) * 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;
|
||||
return (G_V * G_L * common) / NV;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -102,9 +109,11 @@ float bsdf_ggx_refract(vec3 N, vec3 L, vec3 V, float roughness, float eta)
|
||||
* Not really a microfacet model but fits this file.
|
||||
* \{ */
|
||||
|
||||
float bsdf_lambert(vec3 N, vec3 L)
|
||||
float bsdf_lambert(vec3 N, vec3 L, out float pdf)
|
||||
{
|
||||
return saturate(dot(N, L));
|
||||
float cos_theta = saturate(dot(N, L));
|
||||
pdf = cos_theta;
|
||||
return cos_theta;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
/** \name Microfacet GGX distribution
|
||||
* \{ */
|
||||
|
||||
#define GGX_USE_VISIBLE_NORMAL 1
|
||||
|
||||
float sample_pdf_ggx_refract(
|
||||
float sample_pdf_ggx_refract_ex(
|
||||
float NH, float NV, float VH, float LH, float G1_V, float alpha, float eta)
|
||||
{
|
||||
float a2 = square(alpha);
|
||||
@@ -24,6 +22,26 @@ float sample_pdf_ggx_refract(
|
||||
return (D * G1_V * abs(VH * LH) * square(eta)) / (NV * Ht2);
|
||||
}
|
||||
|
||||
/* All inputs must be in tangent space. */
|
||||
float sample_pdf_ggx_refract(vec3 Vt, vec3 Lt, float alpha, float ior, bool is_second_event)
|
||||
{
|
||||
/* Inverse of `refract(-V, H, 1.0 / ior)` with `Lt * ior + Vt` equivalent to `Lt + Vt / ior`. */
|
||||
vec3 Ht = normalize(-(Lt * ior + Vt));
|
||||
/* FIXME(fclem): Why is this necessary? */
|
||||
Ht = is_second_event ? -Ht : Ht;
|
||||
float NH = Ht.z;
|
||||
float NV = Vt.z;
|
||||
float VH = dot(Vt, Ht);
|
||||
float LH = dot(Lt, Ht);
|
||||
|
||||
if (VH > 0.0) {
|
||||
vec3 Vh = normalize(vec3(alpha * Vt.xy, Vt.z));
|
||||
float G1_V = 2.0 * Vh.z / (1.0 + Vh.z);
|
||||
return sample_pdf_ggx_refract_ex(NH, NV, VH, LH, G1_V, alpha, ior);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tangent space microfacet normal following the GGX distribution.
|
||||
*
|
||||
@@ -34,7 +52,6 @@ float sample_pdf_ggx_refract(
|
||||
*/
|
||||
vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt, out float G1_V)
|
||||
{
|
||||
#if GGX_USE_VISIBLE_NORMAL
|
||||
/* Sampling Visible GGX Normals with Spherical Caps.
|
||||
* Jonathan Dupuy and Anis Benyoub, HPG Vol. 42, No. 8, 2023.
|
||||
* https://diglib.eg.org/bitstream/handle/10.1111/cgf14867/v42i8_03_14867.pdf
|
||||
@@ -56,15 +73,6 @@ vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt, out float G1_V)
|
||||
|
||||
/* Transforming the normal back to the ellipsoid configuration. */
|
||||
return normalize(vec3(alpha * Hh.xy, max(0.0, Hh.z)));
|
||||
#else
|
||||
/* Theta is the cone angle. */
|
||||
float z = sqrt((1.0 - rand.x) / (1.0 + square(alpha) * rand.x - rand.x)); /* cos theta */
|
||||
float r = sqrt(max(0.0, 1.0 - z * z)); /* sin theta */
|
||||
float x = r * rand.y;
|
||||
float y = r * rand.z;
|
||||
/* Microfacet Normal */
|
||||
return vec3(x, y, z);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt)
|
||||
@@ -73,6 +81,28 @@ vec3 sample_ggx(vec3 rand, float alpha, vec3 Vt)
|
||||
return sample_ggx(rand, alpha, Vt, G1_unused);
|
||||
}
|
||||
|
||||
/* All inputs must be in tangent space. */
|
||||
float sample_pdf_ggx_bounded(vec3 Vt, vec3 Lt, float alpha)
|
||||
{
|
||||
/**
|
||||
* Eto, Kenta, and Yusuke Tokuyoshi. "Bounded VNDF Sampling for Smith-GGX Reflections."
|
||||
* SIGGRAPH Asia 2023 Technical Communications. 2023. 1-4.
|
||||
* https://gpuopen.com/download/publications/Bounded_VNDF_Sampling_for_Smith-GGX_Reflections.pdf
|
||||
* Listing 2.
|
||||
*/
|
||||
vec3 Ht = normalize(Vt + Lt);
|
||||
float a2 = square(alpha);
|
||||
float s2 = square(1.0 + length(Vt.xy));
|
||||
float D = bxdf_ggx_D(Ht.z, a2);
|
||||
float len_ai_sqr = length_squared(alpha * Vt.xy);
|
||||
float t = sqrt(len_ai_sqr + square(Vt.z));
|
||||
if (Vt.z >= 0.0) {
|
||||
float k = (1.0 - a2) * s2 / (s2 + a2 * square(Vt.z));
|
||||
return D / (2.0 * (k * Vt.z + t));
|
||||
}
|
||||
return D * (t - Vt.z) / (2.0 * len_ai_sqr);
|
||||
}
|
||||
|
||||
/* Similar as `sample_ggx()`, but reduces the number or rejected samples due to reflection in the
|
||||
* lower hemisphere, and returns `pdf` instead of `G1_V`. Only used for reflection.
|
||||
*
|
||||
@@ -103,7 +133,7 @@ vec3 sample_ggx_bounded(vec3 rand, float alpha, vec3 Vt, out float pdf)
|
||||
/* Transforming the normal back to the ellipsoid configuration. */
|
||||
vec3 Ht = normalize(vec3(alpha * Hh.xy, max(0.0, Hh.z)));
|
||||
|
||||
pdf = 0.5 * bxdf_ggx_D(saturate(Ht.z), a2) / (k * Vt.z + norm);
|
||||
pdf = bxdf_ggx_D(saturate(Ht.z), a2) / (2.0 * (k * Vt.z + norm));
|
||||
|
||||
return Ht;
|
||||
}
|
||||
@@ -160,7 +190,7 @@ vec3 sample_ggx_refract(
|
||||
if (VH > 0.0) {
|
||||
vec3 L = refract(-V, H, 1.0 / ior);
|
||||
float LH = dot(L, H);
|
||||
pdf = sample_pdf_ggx_refract(NH, NV, VH, LH, G1_V, alpha, ior);
|
||||
pdf = sample_pdf_ggx_refract_ex(NH, NV, VH, LH, G1_V, alpha, ior);
|
||||
return L;
|
||||
}
|
||||
pdf = 0.0;
|
||||
|
||||
@@ -214,8 +214,6 @@ HorizonScanResult horizon_scan_eval(vec3 vP,
|
||||
* explained in the paper...). Likely to be wrong, but we need a soft falloff. */
|
||||
float facing_weight = saturate(-dot(sample_normal, vL_front) * 2.0);
|
||||
|
||||
float sample_weight = facing_weight * bsdf_lambert(vN, vL_front);
|
||||
|
||||
/* Angular bias shrinks the visibility bitmask around the projected normal. */
|
||||
vec2 biased_theta = (theta - vN_angle) * angle_bias;
|
||||
uint sample_bitmask = horizon_scan_angles_to_bitmask(biased_theta);
|
||||
|
||||
@@ -18,39 +18,43 @@
|
||||
#pragma BLENDER_REQUIRE(draw_view_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_codegen_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(gpu_shader_math_matrix_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_gbuffer_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_ray_types_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_bxdf_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_bxdf_sampling_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_closure_lib.glsl)
|
||||
#pragma BLENDER_REQUIRE(eevee_thickness_lib.glsl)
|
||||
|
||||
float bxdf_eval(ClosureUndetermined cl, vec3 L, vec3 V, float thickness)
|
||||
float pdf_eval(ClosureUndetermined cl, vec3 L, vec3 V, float thickness)
|
||||
{
|
||||
mat3 tangent_to_world = from_up_axis(cl.N);
|
||||
vec3 Vt = V * tangent_to_world;
|
||||
vec3 Lt = L * tangent_to_world;
|
||||
switch (cl.type) {
|
||||
case CLOSURE_BSDF_TRANSLUCENT_ID:
|
||||
if (thickness != 0.0) {
|
||||
/* Uniform sphere weighting. */
|
||||
return 1.0;
|
||||
case CLOSURE_BSDF_TRANSLUCENT_ID: {
|
||||
if (thickness > 0.0) {
|
||||
return sample_pdf_uniform_sphere();
|
||||
}
|
||||
return bsdf_lambert(-cl.N, L);
|
||||
return sample_pdf_cosine_hemisphere(saturate(-Lt.z));
|
||||
}
|
||||
case CLOSURE_BSSRDF_BURLEY_ID:
|
||||
case CLOSURE_BSDF_DIFFUSE_ID:
|
||||
return bsdf_lambert(cl.N, L);
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID:
|
||||
return bsdf_ggx_reflect(
|
||||
cl.N, L, V, square(max(BSDF_ROUGHNESS_THRESHOLD, to_closure_reflection(cl).roughness)));
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
|
||||
return bsdf_ggx_refract(
|
||||
cl.N,
|
||||
L,
|
||||
V,
|
||||
square(max(BSDF_ROUGHNESS_THRESHOLD, to_closure_refraction(cl).roughness)),
|
||||
to_closure_refraction(cl).ior);
|
||||
case CLOSURE_NONE_ID:
|
||||
default:
|
||||
return 0.0;
|
||||
case CLOSURE_BSDF_DIFFUSE_ID: {
|
||||
return sample_pdf_cosine_hemisphere(saturate(Lt.z));
|
||||
}
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID: {
|
||||
float roughness = max(BSDF_ROUGHNESS_THRESHOLD, to_closure_reflection(cl).roughness);
|
||||
return sample_pdf_ggx_bounded(Vt, Lt, square(roughness));
|
||||
}
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
|
||||
float ior = to_closure_refraction(cl).ior;
|
||||
float roughness = max(BSDF_ROUGHNESS_THRESHOLD, to_closure_refraction(cl).roughness);
|
||||
return sample_pdf_ggx_refract(Vt, Lt, square(roughness), ior, thickness != 0.0);
|
||||
}
|
||||
}
|
||||
/* TODO(fclem): Assert. */
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
void transmission_thickness_amend_closure(inout ClosureUndetermined cl,
|
||||
@@ -61,8 +65,8 @@ void transmission_thickness_amend_closure(inout ClosureUndetermined cl,
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
|
||||
float ior = to_closure_refraction(cl).ior;
|
||||
float roughness = to_closure_refraction(cl).roughness;
|
||||
roughness = refraction_roughness_remapping(roughness, ior);
|
||||
vec3 L = refraction_dominant_dir(cl.N, V, ior, roughness);
|
||||
float apparent_roughness = refraction_roughness_remapping(roughness, ior);
|
||||
vec3 L = refraction_dominant_dir(cl.N, V, ior, apparent_roughness);
|
||||
cl.N = -thickness_shape_intersect(thickness, cl.N, L).hit_N;
|
||||
cl.data.y = 1.0 / ior;
|
||||
V = -L;
|
||||
@@ -193,8 +197,10 @@ void main()
|
||||
closest_hit_time = min(closest_hit_time, ray_time);
|
||||
|
||||
/* Slide 54. */
|
||||
/* TODO(fclem): Apparently, ratio estimator should be pdf_bsdf / pdf_ray. */
|
||||
float weight = bxdf_eval(closure, ray_direction, V, thickness) * ray_pdf_inv;
|
||||
/* The reference is wrong.
|
||||
* The ratio estimator is `pdf_local / pdf_ray` instead of `bsdf_local / pdf_ray`. */
|
||||
float pdf = pdf_eval(closure, ray_direction, V, thickness);
|
||||
float weight = pdf * ray_pdf_inv;
|
||||
|
||||
radiance_accum += ray_radiance.rgb * weight;
|
||||
weight_accum += weight;
|
||||
|
||||
@@ -38,7 +38,7 @@ BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V, fl
|
||||
BsdfSample samp;
|
||||
switch (cl.type) {
|
||||
case CLOSURE_BSDF_TRANSLUCENT_ID:
|
||||
if (thickness != 0.0) {
|
||||
if (thickness > 0.0) {
|
||||
/* When modeling object thickness as a sphere, the outgoing rays are distributed uniformly
|
||||
* over the sphere. We don't need the RAY_BIAS in this case. */
|
||||
samp.direction = sample_sphere(noise);
|
||||
@@ -63,7 +63,7 @@ BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V, fl
|
||||
case CLOSURE_BSDF_MICROFACET_GGX_REFLECTION_ID: {
|
||||
if (is_singular_ray(to_closure_reflection(cl).roughness)) {
|
||||
samp.direction = reflect(-V, cl.N);
|
||||
samp.pdf = 1.0;
|
||||
samp.pdf = 1e6;
|
||||
}
|
||||
else {
|
||||
samp.direction = sample_ggx_reflect(random_point_on_cylinder,
|
||||
@@ -91,7 +91,7 @@ BsdfSample ray_generate_direction(vec2 noise, ClosureUndetermined cl, vec3 V, fl
|
||||
|
||||
if (is_singular_ray(roughness)) {
|
||||
samp.direction = refract(-V, cl.N, 1.0 / ior);
|
||||
samp.pdf = 1.0;
|
||||
samp.pdf = 1e6;
|
||||
}
|
||||
else {
|
||||
samp.direction = sample_ggx_refract(random_point_on_cylinder,
|
||||
|
||||
Reference in New Issue
Block a user