EEVEE-Next: Improve shadow bias

The goal of this PR is to remove any user facing parameter
bias that fixes issues that are caused by inherent nature of
the shadow map (aliasing or discretization).

Compute shadow bias in the normal direction to avoid
both shadow leaking at certain angles or shadow
acnee.

This bias is computed automatically based on the minimum
bias amount required to remove all errors. The render
setting is removed as of no use for now.

#### Normal bias
We do the bias in world space instead of shadow space
for speed and simplicity. This requires us to bias using
the upper bound of biases for the same location in space
(using biggest texel world radius instead of UVZ bias).
This isn't much of an issue since the bias is still less
than 2 texel. The bias is still modulated by facing
ratio to the light so surfaces facing the light have no
biase.

This fixes both self shadowing and flat occluders aliasing
artifacts at the cost of moving the shadow a bit on the
side. This is the blue arrow in the diagram.

We always bias toward the normal direction instead of the
light direction. This is alike Cycles geometric offset for
the shadow terminator fix. This is better since it does'nt
modify the shading at all.

#### Slope bias
To avoid aliasing issue on zero slope receiver, we still
have to use the slope bias with a size of 1 pixel.

#### PCF filtering
We now parametrize the filter around the normal instead of
using the shadow map local space. This requires to use
a disk filter instead of box, which is also more pleasant
for most light shapes (all except rectangle lights).

Setting the filter around normal avoid overshadowing from
zero slope occluders. This cannot be fixed by more slope bias
in light space PCF. We could fix it in light space by projecting
onto the normal plane but that gives an unbounded bias when `N.L`
is near 0 which causes either missing shadows or self shadow if
using an arbitrary max offset value.

To avoid overshadowing from any surface behind the shading
point, we reflect the offset to always face the light.
Doing so instead of using the perpendicular direction
is better for very sharp geometric angles, has less
numerical precision issue, is symetrical and is cheaper.

To avoid any self shadowing artifact on zero slope receivers
with angled neighbors (like a wall and the floor), we have
to increase the slope bias according to the filter size.
This might be overkill in most situation but I don't feel
this should become a setting and should be kept in sync
with the filter. If it has to become an option, it should
simply a factor between unbiased filter and best bias.

#### Shadow terminator

The remaining artifacts are all related to shadow terminator
one way or another. It is always caused by the shading
normal we use for biasing and visibility computation not
being aligned with the geometric normal.

This is still something we need a setting for somewhere.

Pull Request: https://projects.blender.org/blender/blender/pulls/121088
This commit is contained in:
Clément Foucault
2024-04-27 14:30:52 +02:00
committed by Clément Foucault
parent 423df70129
commit bb44bce95c
22 changed files with 144 additions and 134 deletions

View File

@@ -775,9 +775,6 @@ class RENDER_PT_eevee_next_sampling_shadows(RenderButtonsPanel, Panel):
col.prop(props, "shadow_ray_count", text="Rays")
col.prop(props, "shadow_step_count", text="Steps")
col = layout.column()
col.prop(props, "shadow_normal_bias", text="Normal Bias")
col = layout.column(align=False, heading="Volume Shadows")
row = col.row(align=True)
sub = row.row(align=True)

View File

@@ -2541,12 +2541,11 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!DNA_struct_member_exists(fd->filesdna, "SceneEEVEE", "float", "shadow_normal_bias")) {
if (!DNA_struct_member_exists(fd->filesdna, "SceneEEVEE", "int", "shadow_step_count")) {
SceneEEVEE default_scene_eevee = *DNA_struct_default_get(SceneEEVEE);
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
scene->eevee.shadow_ray_count = default_scene_eevee.shadow_ray_count;
scene->eevee.shadow_step_count = default_scene_eevee.shadow_step_count;
scene->eevee.shadow_normal_bias = default_scene_eevee.shadow_normal_bias;
}
}

View File

@@ -1237,6 +1237,11 @@ struct ShadowTileMapData {
float half_size;
/** Offset in local space to the tilemap center in world units. Used for directional winmat. */
float2 center_offset;
/** Filter radius of the light in pixels. */
float filter_radius;
float _pad0;
float _pad1;
float _pad2;
};
BLI_STATIC_ASSERT_ALIGN(ShadowTileMapData, 16)
@@ -1254,8 +1259,8 @@ struct ShadowRenderView {
float clip_distance_inv;
/* Viewport to submit the geometry of this tilemap view to. */
uint viewport_index;
uint _pad0;
/* Filter radius for this view. */
float filter_radius;
uint _pad1;
};
BLI_STATIC_ASSERT_ALIGN(ShadowRenderView, 16)
@@ -1474,10 +1479,10 @@ struct ShadowSceneData {
int ray_count;
/* Number of shadow samples to take for each shadow ray. */
int step_count;
/* Bias the shading point by using the normal to avoid self intersection. */
float normal_bias;
/* Ratio between tile-map pixel world "radius" and film pixel world "radius". */
float tilemap_projection_ratio;
float _pad0;
};
BLI_STATIC_ASSERT_ALIGN(ShadowSceneData, 16)

View File

@@ -30,15 +30,19 @@ void ShadowTileMap::sync_orthographic(const float4x4 &object_mat_,
int2 origin_offset,
int clipmap_level,
float lod_bias_,
float filter_radius_,
eShadowProjectionType projection_type_)
{
if (projection_type != projection_type_ || (level != clipmap_level)) {
if ((projection_type != projection_type_) || (level != clipmap_level) ||
(filter_radius != filter_radius_))
{
set_dirty();
}
projection_type = projection_type_;
level = clipmap_level;
light_type = eLightType::LIGHT_SUN;
is_area_side = false;
filter_radius = filter_radius_;
if (grid_shift == int2(0)) {
/* Only replace shift if it is not already dirty. */
@@ -78,6 +82,7 @@ void ShadowTileMap::sync_cubeface(eLightType light_type_,
float side_,
float shift,
eCubeFace face,
float filter_radius_,
float lod_bias_)
{
if (projection_type != SHADOW_PROJECTION_CUBEFACE || (cubeface != face)) {
@@ -90,10 +95,13 @@ void ShadowTileMap::sync_cubeface(eLightType light_type_,
light_type = light_type_;
is_area_side = is_area_light(light_type) && (face != eCubeFace::Z_NEG);
if ((clip_near != near_) || (clip_far != far_) || (half_size != side_)) {
if ((clip_near != near_) || (filter_radius != filter_radius_) || (clip_far != far_) ||
(half_size != side_))
{
set_dirty();
}
filter_radius = filter_radius_;
clip_near = near_;
clip_far = far_;
area_shift = shift;
@@ -377,20 +385,21 @@ void ShadowPunctual::end_sync(Light &light, float lod_bias)
tilemaps_.append(tilemap_pool.acquire());
}
tilemaps_[Z_NEG]->sync_cubeface(light.type, obmat_tmp, near, far, side, shift, Z_NEG, lod_bias);
tilemaps_[Z_NEG]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Z_NEG, light.pcf_radius, lod_bias);
if (tilemaps_needed_ >= 5) {
tilemaps_[X_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, X_POS, lod_bias);
light.type, obmat_tmp, near, far, side, shift, X_POS, light.pcf_radius, lod_bias);
tilemaps_[X_NEG]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, X_NEG, lod_bias);
light.type, obmat_tmp, near, far, side, shift, X_NEG, light.pcf_radius, lod_bias);
tilemaps_[Y_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Y_POS, lod_bias);
light.type, obmat_tmp, near, far, side, shift, Y_POS, light.pcf_radius, lod_bias);
tilemaps_[Y_NEG]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Y_NEG, lod_bias);
light.type, obmat_tmp, near, far, side, shift, Y_NEG, light.pcf_radius, lod_bias);
}
if (tilemaps_needed_ == 6) {
tilemaps_[Z_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Z_POS, lod_bias);
light.type, obmat_tmp, near, far, side, shift, Z_POS, light.pcf_radius, lod_bias);
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
@@ -535,7 +544,8 @@ void ShadowDirectional::cascade_tilemaps_distribution(Light &light, const Camera
/* Equal spacing between cascades layers since we want uniform shadow density. */
int2 level_offset = origin_offset +
shadow_cascade_grid_offset(light.sun.clipmap_base_offset_pos, i);
tilemap->sync_orthographic(object_mat_, level_offset, level, 0.0f, SHADOW_PROJECTION_CASCADE);
tilemap->sync_orthographic(
object_mat_, level_offset, level, 0.0f, light.pcf_radius, SHADOW_PROJECTION_CASCADE);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
@@ -602,7 +612,7 @@ void ShadowDirectional::clipmap_tilemaps_distribution(Light &light,
int2 level_offset = int2(math::round(light_space_camera_position / tile_size));
tilemap->sync_orthographic(
object_mat_, level_offset, level, lod_bias, SHADOW_PROJECTION_CLIPMAP);
object_mat_, level_offset, level, lod_bias, light.pcf_radius, SHADOW_PROJECTION_CLIPMAP);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
@@ -770,7 +780,6 @@ void ShadowModule::init()
data_.ray_count = clamp_i(inst_.scene->eevee.shadow_ray_count, 1, SHADOW_MAX_RAY);
data_.step_count = clamp_i(inst_.scene->eevee.shadow_step_count, 1, SHADOW_MAX_STEP);
data_.normal_bias = max_ff(inst_.scene->eevee.shadow_normal_bias, 0.0f);
/* Pool size is in MBytes. */
const size_t pool_byte_size = enabled_ ? scene.eevee.shadow_pool_size * square_i(1024) : 1;

View File

@@ -101,6 +101,7 @@ struct ShadowTileMap : public ShadowTileMapData {
int2 origin_offset,
int clipmap_level,
float lod_bias_,
float filter_radius,
eShadowProjectionType projection_type_);
void sync_cubeface(eLightType light_type_,
@@ -110,6 +111,7 @@ struct ShadowTileMap : public ShadowTileMapData {
float side,
float shift,
eCubeFace face,
float filter_radius,
float lod_bias_);
void debug_draw() const;

View File

@@ -14,6 +14,7 @@ void main()
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
shadow_flat.filter_radius = render_view_buf[drw_view_id].filter_radius;
#endif
init_interface();

View File

@@ -14,6 +14,7 @@ void main()
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
shadow_flat.filter_radius = render_view_buf[drw_view_id].filter_radius;
#endif
init_interface();

View File

@@ -13,6 +13,7 @@ void main()
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
shadow_flat.filter_radius = render_view_buf[drw_view_id].filter_radius;
#endif
init_interface();

View File

@@ -15,6 +15,7 @@ void main()
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
shadow_flat.filter_radius = render_view_buf[drw_view_id].filter_radius;
#endif
init_interface();

View File

@@ -69,6 +69,11 @@ vec3 light_world_to_local(LightData light, vec3 L)
return transform_direction_transposed(light.object_to_world, L);
}
vec3 light_world_to_local_point(LightData light, vec3 point)
{
return transform_point_inversed(light.object_to_world, point);
}
/* From Frostbite PBR Course
* Distance based attenuation
* http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf */

View File

@@ -13,9 +13,9 @@
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(draw_view_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(common_aabb_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
vec3 safe_project(mat4 winmat, mat4 viewmat, inout int clipped, vec3 v)
{

View File

@@ -89,7 +89,7 @@ void shadow_tag_usage_tilemap_punctual(
return;
}
vec3 lP = light_world_to_local(light, P - light_position_get(light));
vec3 lP = light_world_to_local_point(light, P);
float dist_to_light = max(length(lP) - radius, 1e-5);
if (dist_to_light > light_local_data_get(light).influence_radius_max) {
return;
@@ -112,7 +112,7 @@ void shadow_tag_usage_tilemap_punctual(
lP += vec3(0.0, 0.0, -light_local_data_get(light).shadow_projection_shift);
float footprint_ratio = shadow_punctual_footprint_ratio(
light, P, drw_view_is_perspective(), dist_to_cam, tilemap_proj_ratio);
light, lP, drw_view_is_perspective(), dist_to_cam, tilemap_proj_ratio);
if (radius == 0) {
int face_id = shadow_punctual_face_index_get(lP);

View File

@@ -11,9 +11,9 @@
*/
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_tilemap_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_light_iter_lib.glsl)
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
shared int global_min;
shared int global_max;

View File

@@ -105,6 +105,10 @@ void main()
/* Setup the view. */
render_view_buf[view_index].viewport_index = viewport_index;
/* Scale by actual radius size (overestimate since scaled by bounding circle). */
float filter_radius = tilemap_data.filter_radius * M_SQRT2;
/* We need a minimum slope bias even if filter is 0 to avoid some invalid shadowing. */
render_view_buf[view_index].filter_radius = max(1.0, filter_radius);
/* Clipping setup. */
if (tilemap_data.is_area_side) {
/* Negative for tagging this case. See shadow_clip_vector_get for explanation. */

View File

@@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(common_shape_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl)
/* ---------------------------------------------------------------------- */
@@ -139,7 +140,7 @@ int shadow_directional_level(LightData light, vec3 lP)
/* How much a tilemap pixel covers a final image pixel. */
float shadow_punctual_footprint_ratio(LightData light,
vec3 P,
vec3 lP,
bool is_perspective,
float dist_to_cam,
float tilemap_projection_ratio)
@@ -149,15 +150,14 @@ float shadow_punctual_footprint_ratio(LightData light,
* This gives a good approximation of what LOD to select to get a somewhat uniform shadow map
* resolution in screen space. */
float dist_to_light = distance(P, light_position_get(light));
float footprint_ratio = dist_to_light;
float dist_to_light = length(lP);
/* Apply resolution ratio. */
float footprint_ratio = dist_to_light * tilemap_projection_ratio;
/* Project the radius to the screen. 1 unit away from the camera the same way
* pixel_world_radius_inv was computed. Not needed in orthographic mode. */
if (is_perspective) {
footprint_ratio /= dist_to_cam;
}
/* Apply resolution ratio. */
footprint_ratio *= tilemap_projection_ratio;
/* Take the frustum padding into account. */
footprint_ratio *= light_local_data_get(light).clip_side /
orderedIntBitsToFloat(light.clip_near);

View File

@@ -405,103 +405,87 @@ SHADOW_MAP_TRACE_FN(ShadowRayPunctual)
/* Compute the world space offset of the shading position required for
* stochastic percentage closer filtering of shadow-maps. */
vec3 shadow_pcf_offset(LightData light, const bool is_directional, vec3 P, vec3 Ng, vec2 random)
vec3 shadow_pcf_offset(
LightData light, const bool is_directional, vec3 L, vec3 Ng, float texel_radius, vec2 random)
{
if (light.pcf_radius <= 0.001) {
/* Early return. */
return vec3(0.0);
}
vec3 L = light_vector_get(light, is_directional, P).L;
if (dot(L, Ng) < 0.001) {
/* Don't apply PCF to almost perpendicular,
* since we can't project the offset to the surface. */
return vec3(0.0);
/* We choose a random disk distribution because it is rotationally invariant.
* This sames us the trouble of getting the correct orientation for punctual. */
vec2 disk_sample = sample_disk(random) * (texel_radius * light.pcf_radius);
/* Compute the offset as a disk around the normal. */
mat3x3 tangent_frame = from_up_axis(Ng);
vec3 pcf_offset = tangent_frame[0] * disk_sample.x + tangent_frame[1] * disk_sample.y;
if (dot(pcf_offset, L) < 0.0) {
/* Reflect the offset to avoid overshadowing caused by moving the sampling point below another
* polygon behind the shading point. */
pcf_offset = reflect(pcf_offset, L);
}
ShadowSampleParams params;
return pcf_offset;
}
/**
* Returns the world space radius of a shadow map texel at a given position.
* This is a smooth (not discretized to the LOD transitions) conservative (always above actual
* density) estimate value.
*/
float shadow_texel_radius_at_position(LightData light, const bool is_directional, vec3 P)
{
vec3 lP = light_world_to_local_point(light, P);
float scale = 1.0;
if (is_directional) {
params = shadow_directional_sample_params_get(shadow_tilemaps_tx, light, P);
LightSunData sun = light_sun_data_get(light);
if (light.type == LIGHT_SUN) {
/* Simplification of `coverage_get(shadow_directional_level_fractional)`. */
const float narrowing = float(SHADOW_TILEMAP_RES) / (float(SHADOW_TILEMAP_RES) - 1.0001);
scale = length(lP) * narrowing;
scale *= exp2(light.lod_bias);
scale = clamp(scale, float(1 << sun.clipmap_lod_min), float(1 << sun.clipmap_lod_max));
}
else {
/* Uniform distribution everywhere. No distance scaling. */
scale = 1.0 / float(1 << sun.clipmap_lod_min);
}
}
else {
params = shadow_punctual_sample_params_get(light, P);
}
ShadowSamplingTile tile = shadow_tile_data_get(shadow_tilemaps_tx, params);
if (!tile.is_valid) {
return vec3(0.0);
/* FIXME: The returned value seems quite broken as it increases drastically near the view
* position. */
scale = shadow_punctual_footprint_ratio(light,
lP,
drw_view_is_perspective(),
distance(P, drw_view_position()),
uniform_buf.shadow.tilemap_projection_ratio);
/* This gives the size of pixels at Z = 1. */
scale *= exp2(light.lod_bias);
scale = clamp(scale, float(1 << 0), float(1 << SHADOW_TILEMAP_LOD));
/* Now scale by distance to the light. */
scale *= length(lP);
}
/* Footprint of a tilemap at unit distance from the camera. */
const float texel_footprint = 2.0 * M_SQRT2 / SHADOW_MAP_MAX_RES;
return texel_footprint * scale;
}
/* Compute the shadow-map tangent-bitangent matrix. */
float uv_offset = 1.0 / float(SHADOW_MAP_MAX_RES);
vec3 TP, BP;
if (is_directional) {
TP = shadow_directional_reconstruct_position(
params, light, params.uv + vec3(uv_offset, 0.0, 0.0));
BP = shadow_directional_reconstruct_position(
params, light, params.uv + vec3(0.0, uv_offset, 0.0));
}
else {
mat4 wininv = shadow_punctual_projection_perspective_inverse(light);
TP = shadow_punctual_reconstruct_position(
params, wininv, light, params.uv + vec3(uv_offset, 0.0, 0.0));
BP = shadow_punctual_reconstruct_position(
params, wininv, light, params.uv + vec3(0.0, uv_offset, 0.0));
}
/* TODO: Use a mat2x3 (Currently not supported by the Metal backend). */
mat3 TBN = mat3(TP - P, BP - P, Ng);
/* Compute the actual offset. */
vec2 pcf_offset = random * 2.0 - 1.0;
pcf_offset *= light.pcf_radius;
/* Scale the offset based on shadow LOD. */
if (is_directional) {
vec3 lP = light_world_to_local(light, P);
float level = shadow_directional_level_fractional(light, lP - light_position_get(light));
float pcf_scale = mix(0.5, 1.0, fract(level));
pcf_offset *= pcf_scale;
}
else {
bool is_perspective = drw_view_is_perspective();
float dist_to_cam = distance(P, drw_view_position());
float footprint_ratio = shadow_punctual_footprint_ratio(
light, P, is_perspective, dist_to_cam, uniform_buf.shadow.tilemap_projection_ratio);
float lod = -log2(footprint_ratio) + light.lod_bias;
lod = clamp(lod, 0.0, float(SHADOW_TILEMAP_LOD));
float pcf_scale = exp2(lod);
pcf_offset *= pcf_scale;
}
vec3 ws_offset = TBN * vec3(pcf_offset, 0.0);
vec3 offset_P = P + ws_offset;
/* Project the offset position into the surface */
#ifdef GPU_NVIDIA
/* Workaround for a bug in the Nvidia shader compiler.
* If we don't compute L here again, it breaks shadows on reflection probes. */
L = light_vector_get(light, is_directional, P).L;
#endif
if (abs(dot(Ng, L)) > 0.999) {
return ws_offset;
}
offset_P = line_plane_intersect(offset_P, L, P, Ng);
ws_offset = offset_P - P;
if (dot(ws_offset, L) < 0.0) {
/* Project the offset position into the perpendicular plane, since it's closer to the light
* (avoids overshadowing at geometry angles). */
vec3 perpendicular_plane_normal = cross(Ng, normalize(cross(Ng, L)));
offset_P = line_plane_intersect(offset_P, L, P, perpendicular_plane_normal);
ws_offset = offset_P - P;
}
return ws_offset;
/**
* Compute the amount of offset to add to the shading point in the normal direction to avoid self
* shadowing caused by aliasing artifacts. This is on top of the slope bias computed in the shadow
* render shader to avoid aliasing issues of other polygons. The slope bias only fixes the self
* shadowing from the current polygon, which is not enough in cases with adjacent polygons with
* very different slopes.
*/
float shadow_normal_offset(float texel_radius, vec3 Ng, vec3 L)
{
/* Attenuate depending on light angle. */
/* TODO: Should we take the light shape into consideration? */
float cos_theta = abs(dot(Ng, L));
float sin_theta = sqrt(saturate(1.0 - square(cos_theta)));
return texel_radius * sin_theta;
}
/**
@@ -526,28 +510,27 @@ ShadowEvalResult shadow_eval(LightData light,
vec3 blue_noise_3d = utility_tx_fetch(utility_tx, pixel, UTIL_BLUE_NOISE_LAYER).rgb;
vec3 random_shadow_3d = blue_noise_3d + sampling_rng_3D_get(SAMPLING_SHADOW_U);
vec2 random_pcf_2d = fract(blue_noise_3d.xy + sampling_rng_2D_get(SAMPLING_SHADOW_X));
float normal_offset = uniform_buf.shadow.normal_bias;
#else
/* Case of surfel light eval. */
vec3 random_shadow_3d = vec3(0.5);
vec2 random_pcf_2d = vec2(0.0);
/* TODO(fclem): Parameter on irradiance volumes? */
float normal_offset = 0.02;
#endif
P += shadow_pcf_offset(light, is_directional, P, Ng, random_pcf_2d);
/* Shadow map texel radius at the receiver position. */
float texel_radius = shadow_texel_radius_at_position(light, is_directional, P);
P += shadow_pcf_offset(light, is_directional, L, Ng, texel_radius, 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
* But doing so splits 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. */
/* Avoid self intersection with respect to numerical precision. */
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 += N_bias * normal_offset;
P += N_bias * shadow_normal_offset(texel_radius, Ng, L);
vec3 lP = is_directional ? light_world_to_local(light, P) :
light_world_to_local(light, P - light_position_get(light));

View File

@@ -24,7 +24,12 @@ vec4 closure_to_rgba(Closure cl)
void main()
{
float f_depth = gl_FragCoord.z + fwidth(gl_FragCoord.z);
float f_depth = gl_FragCoord.z;
/* Slope bias.
* Note that we always need a minimum slope bias of 1 pixel to avoid slanted surfaces aliasing
* onto facing surfaces.
* IMPORTANT: `fwidth` needs to be inside uniform control flow. */
f_depth += fwidth(f_depth) * shadow_flat.filter_radius;
#ifdef SHADOW_UPDATE_TBDR
/* We need to write to `gl_FragDepth` un-conditionally. So we cannot early exit or use discard. */

View File

@@ -234,11 +234,15 @@ GPU_SHADER_INTERFACE_INFO(eevee_surf_shadow_atomic_iface, "shadow_iface")
GPU_SHADER_INTERFACE_INFO(eevee_surf_shadow_clipping_iface, "shadow_clip")
.smooth(Type::VEC3, "vector");
GPU_SHADER_INTERFACE_INFO(eevee_surf_shadow_flat_iface, "shadow_flat")
.flat(Type::FLOAT, "filter_radius");
GPU_SHADER_CREATE_INFO(eevee_surf_shadow)
.define("DRW_VIEW_LEN", STRINGIFY(SHADOW_VIEW_MAX))
.define("MAT_SHADOW")
.builtins(BuiltinBits::VIEWPORT_INDEX)
.vertex_out(eevee_surf_shadow_clipping_iface)
.vertex_out(eevee_surf_shadow_flat_iface)
.storage_buf(SHADOW_RENDER_VIEW_BUF_SLOT,
Qualifier::READ,
"ShadowRenderView",

View File

@@ -234,12 +234,13 @@ static void test_eevee_shadow_tag_update()
{
ShadowTileMap tilemap(0 * SHADOW_TILEDATA_PER_TILEMAP);
tilemap.sync_cubeface(
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f, 0.0f);
tilemaps_data.append(tilemap);
}
{
ShadowTileMap tilemap(1 * SHADOW_TILEDATA_PER_TILEMAP);
tilemap.sync_orthographic(float4x4::identity(), int2(0), 1, 0.0f, SHADOW_PROJECTION_CLIPMAP);
tilemap.sync_orthographic(
float4x4::identity(), int2(0), 1, 0.0f, 0.0f, SHADOW_PROJECTION_CLIPMAP);
tilemaps_data.append(tilemap);
}
@@ -1543,7 +1544,7 @@ static void test_eevee_shadow_page_mask_ex(int max_view_per_tilemap)
{
ShadowTileMap tilemap(0);
tilemap.sync_cubeface(
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f, 0.0f);
tilemaps_data.append(tilemap);
}

View File

@@ -236,7 +236,6 @@
.shadow_cascade_size = 1024, \
.shadow_ray_count = 1, \
.shadow_step_count = 6, \
.shadow_normal_bias = 0.02f, \
\
.ray_tracing_method = RAYTRACE_EEVEE_METHOD_SCREEN, \
\

View File

@@ -1873,7 +1873,7 @@ typedef struct SceneEEVEE {
int shadow_pool_size;
int shadow_ray_count;
int shadow_step_count;
float shadow_normal_bias;
char _pad[4];
float clamp_surface_direct;
float clamp_surface_indirect;

View File

@@ -8419,13 +8419,6 @@ static void rna_def_scene_eevee(BlenderRNA *brna)
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
prop = RNA_def_property(srna, "shadow_normal_bias", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.001f, 0.1f, 0.001, 3);
RNA_def_property_ui_text(prop, "Shadow Normal Bias", "Move shadows along their normal");
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);
prop = RNA_def_property(srna, "use_shadow_high_bitdepth", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", SCE_EEVEE_SHADOW_HIGH_BITDEPTH);
RNA_def_property_ui_text(prop, "High Bit Depth", "Use 32-bit shadows");