Fix: EEVEE-Next: Shadow with transmission

This fixes a few issues:
- Split of shadows in different direction caused by
  the shadow normal bias.
- Fixes #120599 by reverting to light vector to
  bias the shadow test.

Cleanup now unused variables.
This commit is contained in:
Clément Foucault
2024-04-13 12:07:25 +02:00
parent a4e9224317
commit 16059aca59
6 changed files with 50 additions and 55 deletions

View File

@@ -46,7 +46,6 @@ void main()
}
vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth));
vec3 P_transmit = vec3(0.0);
vec3 Ng = gbuf.surface_N;
vec3 V = drw_world_incident_vector(P);
float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position());
@@ -66,8 +65,8 @@ void main()
vec3 radiance_front = stack.cl[0].light_shadowed;
stack.cl[0] = closure_light_new(cl_transmit, V, P, gbuf.thickness, P_transmit);
light_eval_transmission(stack, P_transmit, Ng, V, vPz);
stack.cl[0] = closure_light_new(cl_transmit, V, gbuf.thickness);
light_eval_transmission(stack, P, Ng, V, vPz);
vec3 radiance_back = stack.cl[0].light_shadowed;

View File

@@ -57,11 +57,10 @@ void main()
}
# endif
vec3 P_transmit = vec3(0.0);
stack.cl[0] = closure_light_new(cl_transmit, V, P, gbuf.thickness, P_transmit);
stack.cl[0] = closure_light_new(cl_transmit, V, gbuf.thickness);
/* Note: Only evaluates `stack.cl[0]`. */
light_eval_transmission(stack, P_transmit, Ng, V, vPz);
light_eval_transmission(stack, P, Ng, V, vPz);
# if 1 /* TODO Limit to SSS. */
if (cl_transmit.type == CLOSURE_BSSRDF_BURLEY_ID) {

View File

@@ -41,7 +41,6 @@ void main()
}
vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth));
vec3 P_transmit = vec3(0.0);
vec3 Ng = gbuf.surface_N;
vec3 V = drw_world_incident_vector(P);
float vPz = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position());
@@ -61,8 +60,8 @@ void main()
vec3 radiance_front = stack.cl[0].light_shadowed;
stack.cl[0] = closure_light_new(cl_transmit, V, P, gbuf.thickness, P_transmit);
light_eval_transmission(stack, P_transmit, Ng, V, vPz);
stack.cl[0] = closure_light_new(cl_transmit, V, gbuf.thickness);
light_eval_transmission(stack, P, Ng, V, vPz);
vec3 radiance_back = stack.cl[0].light_shadowed;

View File

@@ -43,11 +43,10 @@ void forward_lighting_eval(float thickness, out vec3 radiance, out vec3 transmit
}
# endif
vec3 P_transmit = vec3(0.0);
stack.cl[0] = closure_light_new(cl_transmit, V, g_data.P, thickness, P_transmit);
stack.cl[0] = closure_light_new(cl_transmit, V, thickness);
/* Note: Only evaluates `stack.cl[0]`. */
light_eval_transmission(stack, P_transmit, g_data.Ng, V, vPz);
light_eval_transmission(stack, g_data.P, g_data.Ng, V, vPz);
# if defined(MAT_SUBSURFACE)
if (cl_transmit.type == CLOSURE_BSSRDF_BURLEY_ID) {

View File

@@ -70,7 +70,7 @@ void light_shadow_single(uint l_idx,
#endif
ShadowEvalResult result = shadow_eval(
light, is_directional, is_transmission, P, Ng, ray_count, ray_step_count);
light, is_directional, is_transmission, false, P, Ng, Ng, ray_count, ray_step_count);
shadow_bits |= shadow_pack(result.light_visibilty, ray_count, shift);
shift += ray_count;
@@ -101,11 +101,13 @@ struct ClosureLight {
vec3 N;
/* Enum used as index to fetch which light intensity to use [0..3]. */
LightingType type;
/* Is a translucent BSDF with thickness greater than 0. */
bool is_translucent_with_thickness;
/* Output both shadowed and unshadowed for shadow denoising. */
vec3 light_shadowed;
vec3 light_unshadowed;
/** Only for transmission BSDFs. */
vec3 shading_offset;
float shadow_offset;
};
struct ClosureLightStack {
@@ -115,10 +117,8 @@ struct ClosureLightStack {
ClosureLight closure_light_new_ex(ClosureUndetermined cl,
vec3 V,
vec3 P,
float thickness,
const bool is_transmission,
out vec3 out_P)
const bool is_transmission)
{
ClosureLight cl_light;
cl_light.N = cl.N;
@@ -126,27 +126,20 @@ ClosureLight closure_light_new_ex(ClosureUndetermined cl,
cl_light.type = LIGHT_DIFFUSE;
cl_light.light_shadowed = vec3(0.0);
cl_light.light_unshadowed = vec3(0.0);
cl_light.is_translucent_with_thickness = false;
cl_light.shading_offset = vec3(0.0);
cl_light.shadow_offset = is_transmission ? thickness : 0.0;
switch (cl.type) {
case CLOSURE_BSDF_TRANSLUCENT_ID:
if (is_transmission) {
cl_light.N = -cl.N;
cl_light.type = LIGHT_DIFFUSE;
out_P = P;
if (thickness > 0.0) {
vec2 random_2d = pcg3d(P).xy;
#ifdef EEVEE_SAMPLING_DATA
random_2d = fract(random_2d + sampling_rng_2D_get(SAMPLING_SHADOW_X));
#endif
float radius = thickness * 0.5;
/* Store random shadow position inside the normal since it has no effect. */
cl_light.N = vec3(sample_disk(random_2d) * radius, radius);
/* Strangely, a translucent sphere lit by a light outside the sphere transmits the light
* uniformly over the sphere. To mimic this phenomenon, we shift the shading position to
* a unique position on the sphere and use the light vector as normal. */
out_P -= cl.N * radius;
cl_light.is_translucent_with_thickness = true;
cl_light.shading_offset = -cl.N * thickness * 0.5;
cl_light.N = vec3(0.0);
}
}
break;
@@ -158,8 +151,7 @@ ClosureLight closure_light_new_ex(ClosureUndetermined cl,
cl_light.N = -cl.N;
cl_light.type = LIGHT_DIFFUSE;
/* Lit and shadow as outside of the object. */
out_P = P - cl.N * thickness;
break;
cl_light.shading_offset = -cl.N * thickness;
}
/* Reflection term uses the lambertian diffuse. */
break;
@@ -171,7 +163,6 @@ ClosureLight closure_light_new_ex(ClosureUndetermined cl,
break;
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID: {
if (is_transmission) {
out_P = P;
ClosureRefraction cl_refract = to_closure_refraction(cl);
cl_refract.roughness = refraction_roughness_remapping(cl_refract.roughness,
cl_refract.ior);
@@ -181,7 +172,7 @@ ClosureLight closure_light_new_ex(ClosureUndetermined cl,
ThicknessIsect isect = thickness_sphere_intersect(thickness, cl.N, L);
cl.N = -isect.hit_N;
out_P += isect.hit_P;
cl_light.shading_offset = isect.hit_P;
cl_refract.ior = 1.0 / cl_refract.ior;
V = -L;
@@ -200,16 +191,14 @@ ClosureLight closure_light_new_ex(ClosureUndetermined cl,
return cl_light;
}
ClosureLight closure_light_new(
ClosureUndetermined cl, vec3 V, vec3 P, float thickness, out vec3 out_P)
ClosureLight closure_light_new(ClosureUndetermined cl, vec3 V, float thickness)
{
return closure_light_new_ex(cl, V, P, thickness, true, out_P);
return closure_light_new_ex(cl, V, thickness, true);
}
ClosureLight closure_light_new(ClosureUndetermined cl, vec3 V)
{
vec3 unused_P = vec3(0.0), unused_out_P = vec3(0.0);
return closure_light_new_ex(cl, V, unused_P, 0.0, false, unused_out_P);
return closure_light_new_ex(cl, V, 0.0, false);
}
void light_eval_single_closure(LightData light,
@@ -249,10 +238,10 @@ void light_eval_single(uint l_idx,
int ray_step_count = uniform_buf.shadow.step_count;
#endif
LightVector lv = light_vector_get(light, is_directional, P);
vec3 shading_P = (is_transmission) ? P + stack.cl[0].shading_offset : P;
LightVector lv = light_vector_get(light, is_directional, shading_P);
bool is_translucent_with_thickness = is_transmission &&
stack.cl[0].is_translucent_with_thickness;
bool is_translucent_with_thickness = is_transmission && all(equal(stack.cl[0].N, vec3(0.0)));
float attenuation = light_attenuation_surface(
light, is_directional, is_transmission, is_translucent_with_thickness, Ng, lv);
@@ -267,14 +256,16 @@ void light_eval_single(uint l_idx,
shift += ray_count;
#else
if (is_translucent_with_thickness) {
/* Translucent doesn't use the normal for shading.
* Instead it stores the random shadow position offsets. */
P += from_up_axis(lv.L) * stack.cl[0].N;
}
ShadowEvalResult result = shadow_eval(
light, is_directional, is_transmission, P, Ng, ray_count, ray_step_count);
vec3 shadow_P = (is_transmission) ? P + lv.L * stack.cl[0].shadow_offset : P;
ShadowEvalResult result = shadow_eval(light,
is_directional,
is_transmission,
is_translucent_with_thickness,
shadow_P,
Ng,
lv.L,
ray_count,
ray_step_count);
shadow = result.light_visibilty;
#endif
}

View File

@@ -510,8 +510,10 @@ vec3 shadow_pcf_offset(LightData light, const bool is_directional, vec3 P, vec3
ShadowEvalResult shadow_eval(LightData light,
const bool is_directional,
const bool is_transmission,
bool is_translucent_with_thickness,
vec3 P,
vec3 Ng,
vec3 L,
int ray_count,
int ray_step_count)
{
@@ -533,21 +535,27 @@ ShadowEvalResult shadow_eval(LightData light,
float normal_offset = 0.02;
#endif
/* We want to bias inside the object for transmission to go through the object itself. */
normal_offset = is_transmission ? -normal_offset : normal_offset;
P += shadow_pcf_offset(light, is_directional, P, Ng, random_pcf_2d);
/* We want to bias inside the object for transmission to go through the object itself.
* But doing so split the shadow in two different directions at the horizon. Also this
* doesn't fix the the aliasing issue. So we reflect the normal so that it always go towards
* the light. */
vec3 N_bias = is_transmission ? reflect(Ng, L) : Ng;
/* Avoid self intersection. */
P = offset_ray(P, Ng);
P = offset_ray(P, N_bias);
/* The above offset isn't enough in most situation. Still add a bigger bias. */
/* TODO(fclem): Scale based on depth. */
P += Ng * normal_offset;
P += N_bias * normal_offset;
vec3 lP = is_directional ? light_world_to_local(light, P) :
light_world_to_local(light, P - light._position);
vec3 lNg = light_world_to_local(light, Ng);
lNg = is_transmission ? -Ng : Ng;
/* Invert horizon clipping. */
lNg = (is_transmission) ? -lNg : lNg;
/* Don't do a any horizon clipping in this case as the closure is lit from both sides. */
lNg = (is_transmission && is_translucent_with_thickness) ? vec3(0.0) : lNg;
float surface_hit = 0.0;
for (int ray_index = 0; ray_index < ray_count && ray_index < SHADOW_MAX_RAY; ray_index++) {