Fix: EEVEE-Next: Wrong LOD tagging for punctual shadow maps

The root issue is that `shadow_punctual_footprint_ratio` was not
fed with Z distance but by radial distances to camera and
light.

This commit cleans up this computation by only precomputing the
film pixel radius on CPU. This allow the LOD computation to be
more local and easier to read.

Fix #119725

Pull Request: https://projects.blender.org/blender/blender/pulls/121167
This commit is contained in:
Clément Foucault 2024-04-29 15:19:49 +02:00 committed by Clément Foucault
parent 6e137f957f
commit e401d996aa
11 changed files with 89 additions and 82 deletions

@ -1479,8 +1479,8 @@ struct ShadowSceneData {
int ray_count;
/* Number of shadow samples to take for each shadow ray. */
int step_count;
/* Ratio between tile-map pixel world "radius" and film pixel world "radius". */
float tilemap_projection_ratio;
/* Bounding radius for a film pixel at 1 unit from the camera. */
float film_pixel_radius;
float _pad0;
};

@ -876,8 +876,6 @@ void ShadowModule::begin_sync()
/* Directional shadows. */
float texel_size = ShadowDirectional::tile_size_get(0) / float(SHADOW_PAGE_RES);
int directional_level = std::max(0, int(std::ceil(log2(surfel_coverage_area / texel_size))));
/* Punctual shadows. */
float projection_ratio = tilemap_pixel_radius() / (surfel_coverage_area / 2.0);
PassMain::Sub &sub = pass.sub("Surfels");
sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_TAG_USAGE_SURFELS));
@ -886,7 +884,7 @@ void ShadowModule::begin_sync()
sub.bind_ssbo("surfel_buf", &surfels_buf);
sub.bind_ssbo("capture_info_buf", &capture_info_buf);
sub.push_constant("directional_level", directional_level);
sub.push_constant("tilemap_proj_ratio", projection_ratio);
sub.bind_resources(inst_.uniform_data);
sub.bind_resources(inst_.lights);
sub.dispatch(&inst_.volume_probes.bake.dispatch_per_surfel_);
@ -901,7 +899,6 @@ void ShadowModule::begin_sync()
sub.bind_ssbo("tilemaps_buf", &tilemap_pool.tilemaps_data);
sub.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data);
sub.bind_texture("depth_tx", &src_depth_tx_);
sub.push_constant("tilemap_proj_ratio", &data_.tilemap_projection_ratio);
sub.push_constant("input_depth_extent", &input_depth_extent_);
sub.bind_resources(inst_.lights);
sub.bind_resources(inst_.uniform_data);
@ -920,8 +917,6 @@ void ShadowModule::begin_sync()
sub.bind_ssbo("tilemaps_buf", &tilemap_pool.tilemaps_data);
sub.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data);
sub.bind_ssbo("bounds_buf", &manager.bounds_buf.current());
sub.push_constant("tilemap_proj_ratio", &data_.tilemap_projection_ratio);
sub.push_constant("pixel_world_radius", &pixel_world_radius_);
sub.push_constant("fb_resolution", &usage_tag_fb_resolution_);
sub.push_constant("fb_lod", &usage_tag_fb_lod_);
sub.bind_resources(inst_.uniform_data);
@ -1153,7 +1148,6 @@ void ShadowModule::end_sync()
sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_TAG_USAGE_VOLUME));
sub.bind_ssbo("tilemaps_buf", &tilemap_pool.tilemaps_data);
sub.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data);
sub.push_constant("tilemap_proj_ratio", &data_.tilemap_projection_ratio);
sub.bind_resources(inst_.uniform_data);
sub.bind_resources(inst_.hiz_buffer.front);
sub.bind_resources(inst_.sampling);
@ -1334,17 +1328,6 @@ float ShadowModule::screen_pixel_radius(const View &view, const int2 &extent)
return math::distance(p0, p1) / min_dim;
}
/* Compute approximate screen pixel world space radius at 1 unit away of the light. */
float ShadowModule::tilemap_pixel_radius()
{
/* This is a really rough approximation. Ideally, the cube-map distortion should be taken into
* account per pixel. But this would make this pre-computation impossible.
* So for now compute for the center of the cube-map. */
const float cubeface_diagonal = M_SQRT2 * 2.0f;
const float pixel_count = SHADOW_TILEMAP_RES * shadow_page_size_;
return cubeface_diagonal / pixel_count;
}
bool ShadowModule::shadow_update_finished()
{
if (!inst_.is_image_render()) {
@ -1414,8 +1397,7 @@ void ShadowModule::set_view(View &view, int2 extent)
1);
max_view_per_tilemap_ = max_view_per_tilemap();
pixel_world_radius_ = screen_pixel_radius(view, extent);
data_.tilemap_projection_ratio = tilemap_pixel_radius() / pixel_world_radius_;
data_.film_pixel_radius = screen_pixel_radius(view, extent);
inst_.uniform_data.push_update();
usage_tag_fb_resolution_ = math::divide_ceil(extent, int2(std::exp2(usage_tag_fb_lod_)));

@ -259,7 +259,6 @@ class ShadowModule {
ShadowRenderViewBuf render_view_buf_ = {"render_view_buf"};
int3 dispatch_depth_scan_size_;
float pixel_world_radius_;
int2 usage_tag_fb_resolution_;
int usage_tag_fb_lod_ = 5;
int max_view_per_tilemap_ = 1;

@ -41,7 +41,7 @@ float ray_aabb(vec3 ray_origin, vec3 ray_direction, vec3 aabb_min, vec3 aabb_max
float pixel_size_at(float linear_depth)
{
float pixel_size = pixel_world_radius;
float pixel_size = uniform_buf.shadow.film_pixel_radius;
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
if (is_persp) {
pixel_size *= max(0.01, linear_depth);
@ -119,6 +119,6 @@ void main()
vec3 vP = drw_point_world_to_view(P);
shadow_tag_usage(
vP, P, ws_view_direction, step_radius, t, gl_FragCoord.xy * exp2(float(fb_lod)), 0);
vP, P, ws_view_direction, step_radius, gl_FragCoord.xy * exp2(float(fb_lod)), 0);
}
}

@ -80,8 +80,7 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
}
}
void shadow_tag_usage_tilemap_punctual(
uint l_idx, vec3 P, float dist_to_cam, float radius, int lod_bias)
void shadow_tag_usage_tilemap_punctual(uint l_idx, vec3 P, float radius, int lod_bias)
{
LightData light = light_buf[l_idx];
@ -111,18 +110,17 @@ void shadow_tag_usage_tilemap_punctual(
/* TODO(fclem): 3D shift for jittered soft shadows. */
lP += vec3(0.0, 0.0, -light_local_data_get(light).shadow_projection_shift);
float footprint_ratio = shadow_punctual_footprint_ratio(
light, lP, drw_view_is_perspective(), dist_to_cam, tilemap_proj_ratio);
int lod = shadow_punctual_level(light,
lP,
drw_view_is_perspective(),
drw_view_z_distance(P),
uniform_buf.shadow.film_pixel_radius);
lod = clamp(lod + lod_bias, 0, SHADOW_TILEMAP_LOD);
if (radius == 0) {
int face_id = shadow_punctual_face_index_get(lP);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
ShadowCoordinates coord = shadow_punctual_coordinates(light, lP, face_id);
int lod = int(floor(-log2(footprint_ratio) + tilemaps_buf[coord.tilemap_index].lod_bias));
lod += lod_bias;
lod = clamp(lod, 0, SHADOW_TILEMAP_LOD);
shadow_tag_usage_tile(light, coord.tile_coord, lod, coord.tilemap_index);
}
else {
@ -142,10 +140,6 @@ void shadow_tag_usage_tilemap_punctual(
}
int tilemap_index = light.tilemap_index + face_id;
int lod = int(ceil(-log2(footprint_ratio) + tilemaps_buf[tilemap_index].lod_bias));
lod += lod_bias;
lod = clamp(lod, 0, SHADOW_TILEMAP_LOD);
vec3 _lP = shadow_punctual_local_position_to_face_local(face_id, lP);
vec3 offset = vec3(radius, radius, 0);
@ -166,8 +160,7 @@ void shadow_tag_usage_tilemap_punctual(
* Used for downsampled/ray-marched tagging, so all the shadow-map texels covered get correctly
* tagged.
*/
void shadow_tag_usage(
vec3 vP, vec3 P, vec3 V, float radius, float dist_to_cam, vec2 pixel, int lod_bias)
void shadow_tag_usage(vec3 vP, vec3 P, vec3 V, float radius, vec2 pixel, int lod_bias)
{
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
shadow_tag_usage_tilemap_directional(l_idx, P, V, radius, lod_bias);
@ -175,16 +168,14 @@ void shadow_tag_usage(
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx) {
shadow_tag_usage_tilemap_punctual(l_idx, P, dist_to_cam, radius, lod_bias);
shadow_tag_usage_tilemap_punctual(l_idx, P, radius, lod_bias);
}
LIGHT_FOREACH_END
}
void shadow_tag_usage(vec3 vP, vec3 P, vec2 pixel)
{
float dist_to_cam = length(vP);
shadow_tag_usage(vP, P, vec3(0), 0, dist_to_cam, pixel, 0);
shadow_tag_usage(vP, P, vec3(0), 0, pixel, 0);
}
void shadow_tag_usage_surfel(Surfel surfel, int directional_lvl)
@ -198,9 +189,7 @@ void shadow_tag_usage_surfel(Surfel surfel, int directional_lvl)
LIGHT_FOREACH_BEGIN_LOCAL_NO_CULL(light_cull_buf, l_idx)
{
/* Set distance to camera to 1 to avoid changing footprint_ratio. */
float dist_to_cam = 1.0;
shadow_tag_usage_tilemap_punctual(l_idx, P, dist_to_cam, 0, 0);
shadow_tag_usage_tilemap_punctual(l_idx, P, 0, 0);
}
LIGHT_FOREACH_END
}

@ -20,7 +20,7 @@ void inflate_bounds(vec3 ls_center, inout vec3 P, inout vec3 lP)
{
vec3 vP = drw_point_world_to_view(P);
float inflate_scale = pixel_world_radius * exp2(float(fb_lod));
float inflate_scale = uniform_buf.shadow.film_pixel_radius * exp2(float(fb_lod));
if (drw_view_is_perspective()) {
inflate_scale *= -vP.z;
}

@ -45,5 +45,5 @@ void main()
uniform_buf.volumes.main_view_extent;
int bias = uniform_buf.volumes.tile_size_lod;
shadow_tag_usage(vP, P, drw_world_incident_vector(P), 0.01, length(vP), pixel, bias);
shadow_tag_usage(vP, P, drw_world_incident_vector(P), 0.01, pixel, bias);
}

@ -108,7 +108,6 @@ ShadowSamplingTile shadow_tile_load(usampler2D tilemaps_tx, ivec2 tile_co, int t
* \a lP shading point position in light space, relative to the to camera position snapped to
* the smallest clip-map level (`shadow_world_to_local(light, P) - light_position_get(light)`).
*/
float shadow_directional_level_fractional(LightData light, vec3 lP)
{
float lod;
@ -138,30 +137,66 @@ int shadow_directional_level(LightData light, vec3 lP)
return int(ceil(shadow_directional_level_fractional(light, lP)));
}
/* How much a tilemap pixel covers a final image pixel. */
float shadow_punctual_footprint_ratio(LightData light,
vec3 lP,
bool is_perspective,
float dist_to_cam,
float tilemap_projection_ratio)
float shadow_punctual_frustum_padding_get(LightData light)
{
return light_local_data_get(light).clip_side / orderedIntBitsToFloat(light.clip_near);
}
/**
* Returns the ratio of radius between shadow map pixels and screen pixels.
* `distance_to_camera` is Z distance to the camera origin.
*/
float shadow_punctual_pixel_ratio(LightData light,
vec3 lP,
bool is_perspective,
float distance_to_camera,
float film_pixel_radius)
{
/* We project a shadow map pixel (as a sphere for simplicity) to the receiver plane.
* We then reproject this sphere onto the camera screen and compare it to the film pixel size.
* 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 = 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;
}
float film_footprint = (is_perspective) ? film_pixel_radius * distance_to_camera :
film_pixel_radius;
/* Compute approximate screen pixel world space radius at 1 unit away of the light. */
float shadow_pixel_footprint = 2.0 * M_SQRT2 / SHADOW_MAP_MAX_RES;
/* Take the frustum padding into account. */
footprint_ratio *= light_local_data_get(light).clip_side /
orderedIntBitsToFloat(light.clip_near);
return footprint_ratio;
shadow_pixel_footprint *= shadow_punctual_frustum_padding_get(light);
float distance_to_light = reduce_max(abs(lP));
float shadow_footprint = shadow_pixel_footprint * distance_to_light;
/* TODO(fclem): Ideally, this should be modulated by N.L. */
float ratio = shadow_footprint / film_footprint;
return ratio;
}
/**
* Returns the LOD for a given shadow space position.
* `distance_to_camera` is Z distance to the camera origin.
*/
float shadow_punctual_level_fractional(LightData light,
vec3 lP,
bool is_perspective,
float distance_to_camera,
float film_pixel_radius)
{
float ratio = shadow_punctual_pixel_ratio(
light, lP, is_perspective, distance_to_camera, film_pixel_radius);
/* Note: Bias by one to counteract the ceil in the `int` variant. This is done because this
* function should return an upper bound. */
float lod = -log2(ratio) - 1.0 + light.lod_bias;
lod = clamp(lod, 0.0, float(SHADOW_TILEMAP_LOD));
return lod;
}
int shadow_punctual_level(LightData light,
vec3 lP,
bool is_perspective,
float distance_to_camera,
float film_pixel_radius)
{
return int(ceil(shadow_punctual_level_fractional(
light, lP, is_perspective, distance_to_camera, film_pixel_radius)));
}
struct ShadowCoordinates {

@ -454,18 +454,18 @@ float shadow_texel_radius_at_position(LightData light, const bool is_directional
}
}
else {
/* 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);
/* Simplification of `coverage_get(shadow_punctual_level_fractional)`. */
scale = shadow_punctual_pixel_ratio(light,
lP,
drw_view_is_perspective(),
drw_view_z_distance(P),
uniform_buf.shadow.film_pixel_radius);
/* This gives the size of pixels at Z = 1. */
scale *= exp2(light.lod_bias);
scale = 1.0 / scale;
scale *= exp2(-1.0 + light.lod_bias);
scale = clamp(scale, float(1 << 0), float(1 << SHADOW_TILEMAP_LOD));
/* Now scale by distance to the light. */
scale *= length(lP);
scale *= reduce_max(abs(lP));
}
/* Footprint of a tilemap at unit distance from the camera. */
const float texel_footprint = 2.0 * M_SQRT2 / SHADOW_MAP_MAX_RES;

@ -64,7 +64,6 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_opaque)
.storage_buf(5, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(6, Qualifier::READ_WRITE, SHADOW_TILE_DATA_PACKED, "tiles_buf[]")
.push_constant(Type::IVEC2, "input_depth_extent")
.push_constant(Type::FLOAT, "tilemap_proj_ratio")
.additional_info(
"eevee_shared", "draw_view", "draw_view_culling", "eevee_hiz_data", "eevee_light_data")
.compute_source("eevee_shadow_tag_usage_comp.glsl");
@ -76,11 +75,11 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_surfels)
/* ShadowTileDataPacked is uint. But MSL translation need the real type. */
.storage_buf(7, Qualifier::READ_WRITE, "uint", "tiles_buf[]")
.push_constant(Type::INT, "directional_level")
.push_constant(Type::FLOAT, "tilemap_proj_ratio")
.additional_info("eevee_shared",
"draw_view",
"draw_view_culling",
"eevee_light_data",
"eevee_global_ubo",
"eevee_surfel_common")
.compute_source("eevee_shadow_tag_usage_surfels_comp.glsl");
@ -97,8 +96,6 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_transparent)
.storage_buf(4, Qualifier::READ, "ObjectBounds", "bounds_buf[]")
.storage_buf(5, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(6, Qualifier::READ_WRITE, SHADOW_TILE_DATA_PACKED, "tiles_buf[]")
.push_constant(Type::FLOAT, "tilemap_proj_ratio")
.push_constant(Type::FLOAT, "pixel_world_radius")
.push_constant(Type::IVEC2, "fb_resolution")
.push_constant(Type::INT, "fb_lod")
.vertex_out(eevee_shadow_tag_transparent_iface)
@ -118,7 +115,6 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_volume)
.local_group_size(VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE, VOLUME_GROUP_SIZE)
.storage_buf(4, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(5, Qualifier::READ_WRITE, SHADOW_TILE_DATA_PACKED, "tiles_buf[]")
.push_constant(Type::FLOAT, "tilemap_proj_ratio")
.additional_info("eevee_volume_properties_data",
"eevee_shared",
"draw_view",

@ -24,6 +24,12 @@ vec3 drw_view_position()
return drw_view.viewinv[3].xyz;
}
/* Positive Z distance from the view origin. Faster than using `drw_point_world_to_view`. */
float drw_view_z_distance(vec3 P)
{
return dot(P - drw_view_position(), -drw_view_forward());
}
/* Returns the projection matrix far clip distance. */
float drw_view_far()
{