From 77638a6bec75f1297321060545007e3115330aa4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Cl=C3=A9ment=20Foucault?= Date: Fri, 12 Apr 2024 22:08:38 +0200 Subject: [PATCH] EEVEE-Next: Fix missing shadow caused by ray going below surface This should fix the light leaking present in most curved surfaces. This is completely ad-hoc and should be fixed better. --- .../shaders/eevee_shadow_tracing_lib.glsl | 59 ++++++++++------------ 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl index a9b7dfa393d..2cc071c4340 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_shadow_tracing_lib.glsl @@ -185,6 +185,18 @@ ShadowMapTraceResult shadow_map_trace_finish(ShadowMapTracingState state) /** \} */ +/* If the ray direction `L` is below the horizon defined by N (normalized) at the shading point, + * push it just above the horizon so that this ray will never be below it and produce + * over-shadowing (since light evaluation already clips the light shape). */ +vec3 shadow_ray_above_horizon_ensure(vec3 L, vec3 N) +{ + float distance_to_plan = dot(L, -N); + if (distance_to_plan > 0.0) { + L += N * (0.01 + distance_to_plan); + } + return L; +} + /* ---------------------------------------------------------------------- */ /** \name Directional Shadow Map Tracing * \{ */ @@ -196,8 +208,10 @@ struct ShadowRayDirectional { LightData light; }; -ShadowRayDirectional shadow_ray_generate_directional( - LightData light, vec2 random_2d, vec3 lP, vec3 lNg, out bool r_is_above_surface) +ShadowRayDirectional shadow_ray_generate_directional(LightData light, + vec2 random_2d, + vec3 lP, + vec3 lNg) { float clip_near = orderedIntBitsToFloat(light.clip_near); float clip_far = orderedIntBitsToFloat(light.clip_far); @@ -210,14 +224,11 @@ ShadowRayDirectional shadow_ray_generate_directional( vec3 disk_direction = sample_uniform_cone(sample_cylinder(random_2d), light_sun_data_get(light).shadow_angle); - /* Light shape is 1 unit away from the shading point. */ - vec4 direction = vec4(disk_direction, -1.0 / z_range); - r_is_above_surface = dot(lNg, direction.xyz) > 0.0; + disk_direction = shadow_ray_above_horizon_ensure(disk_direction, lNg); - /* TODO(fclem): Bias sample direction above the horizon (or below if transmission). - * We don't really care about the shadow shape at such light elevation angles but more about - * noise. */ + /* Light shape is 1 unit away from the shading point. */ + vec4 direction = vec4(disk_direction, -1.0 / z_range); /* It only make sense to trace where there can be occluder. Clamp by distance to near plane. */ direction *= min(light_sun_data_get(light).shadow_trace_distance, @@ -285,8 +296,7 @@ struct ShadowRayPunctual { }; /* Return ray in UV clip space [0..1]. */ -ShadowRayPunctual shadow_ray_generate_punctual( - LightData light, vec2 random_2d, vec3 lP, vec3 lNg, out bool r_is_above_surface) +ShadowRayPunctual shadow_ray_generate_punctual(LightData light, vec2 random_2d, vec3 lP, vec3 lNg) { if (light.type == LIGHT_RECT) { random_2d = random_2d * 2.0 - 1.0; @@ -311,11 +321,7 @@ ShadowRayPunctual shadow_ray_generate_punctual( -projection_origin, point_on_light_shape, light_local_data_get(light).shadow_scale); direction = point_on_light_shape - lP; - r_is_above_surface = dot(direction, lNg) > 0.0; - - /* TODO(fclem): Bias sample direction above the horizon (or below if transmission). - * We don't really care about the shadow shape at such light elevation angles but more about - * noise. */ + direction = shadow_ray_above_horizon_ensure(direction, lNg); /* Clip the ray to not cross the near plane. * Scale it so that it encompass the whole cube (with a safety margin). */ @@ -341,11 +347,7 @@ ShadowRayPunctual shadow_ray_generate_punctual( vec3 point_on_light_shape = right * random_2d.x + up * random_2d.y; direction = point_on_light_shape - lP; - r_is_above_surface = dot(direction, lNg) > 0.0; - - /* TODO(fclem): Bias sample direction above the horizon (or below if transmission). - * We don't really care about the shadow shape at such light elevation angles but more about - * noise. */ + direction = shadow_ray_above_horizon_ensure(direction, lNg); /* Clip the ray to not cross the light shape. */ float clip_distance = light_spot_data_get(light).radius; @@ -547,34 +549,25 @@ ShadowEvalResult shadow_eval(LightData light, vec3 lNg = light_world_to_local(light, Ng); float surface_hit = 0.0; - float surface_ray_count = 0.0; for (int ray_index = 0; ray_index < ray_count && ray_index < SHADOW_MAX_RAY; ray_index++) { vec2 random_ray_2d = fract(hammersley_2d(ray_index, ray_count) + random_shadow_3d.xy); - /* We only consider rays above the surface for shadowing. This is because the LTC evaluation - * already accounts for the clipping of the light shape. */ - bool is_above_surface; - ShadowMapTraceResult trace; if (is_directional) { ShadowRayDirectional clip_ray = shadow_ray_generate_directional( - light, random_ray_2d, lP, lNg, is_above_surface); + light, random_ray_2d, lP, lNg); trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z); } else { - ShadowRayPunctual clip_ray = shadow_ray_generate_punctual( - light, random_ray_2d, lP, lNg, is_above_surface); + ShadowRayPunctual clip_ray = shadow_ray_generate_punctual(light, random_ray_2d, lP, lNg); trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z); } - if (is_above_surface != is_transmission) { - surface_hit += float(trace.has_hit); - surface_ray_count += 1.0; - } + surface_hit += float(trace.has_hit); } /* Average samples. */ ShadowEvalResult result; - result.light_visibilty = saturate(1.0 - surface_hit * safe_rcp(surface_ray_count)); + result.light_visibilty = saturate(1.0 - surface_hit * float(ray_count)); result.occluder_distance = 0.0; /* Unused. Could reintroduced if needed. */ return result; } -- 2.11.4.GIT