Fix: EEVEE-Next: Shadow: Casters behind area lights cast shadow #120992

Merged
Clément Foucault merged 5 commits from fclem/blender:fix-119334 into main 2024-04-24 17:08:21 +02:00
15 changed files with 177 additions and 54 deletions

View File

@ -245,7 +245,7 @@
/* Only during shadow rendering. */
#define SHADOW_RENDER_MAP_BUF_SLOT 3
#define SHADOW_PAGE_INFO_SLOT 4
#define SHADOW_VIEWPORT_INDEX_BUF_SLOT 5
#define SHADOW_RENDER_VIEW_BUF_SLOT 5
/* Only during pre-pass. */
#define VELOCITY_OBJ_PREV_BUF_SLOT 0

View File

@ -204,7 +204,7 @@ void ShadowPipeline::sync()
draw::PassMain::Sub &pass = render_ps_.sub("Shadow.Surface");
pass.state_set(state);
pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx);
pass.bind_ssbo(SHADOW_VIEWPORT_INDEX_BUF_SLOT, &inst_.shadows.viewport_index_buf_);
pass.bind_ssbo(SHADOW_RENDER_VIEW_BUF_SLOT, &inst_.shadows.render_view_buf_);
if (!shadow_update_tbdr) {
/* We do not need all of the shadow information when using the TBDR-optimized approach. */
pass.bind_image(SHADOW_ATLAS_IMG_SLOT, inst_.shadows.atlas_tx_);

View File

@ -1146,9 +1146,12 @@ struct ShadowTileMapData {
int clip_data_index;
/** Bias LOD to tag for usage to lower the amount of tile used. */
float lod_bias;
int _pad0;
int _pad1;
int _pad2;
/** Light type this tilemap is from. */
eLightType light_type;
/** True if the tilemap is part of area light shadow and is one of the side projections. */
bool32_t is_area_side;
/** Distance behind the area light a shadow is shifted. */
float area_shift;
/** Near and far clip distances for punctual. */
float clip_near;
float clip_far;
@ -1159,8 +1162,29 @@ struct ShadowTileMapData {
};
BLI_STATIC_ASSERT_ALIGN(ShadowTileMapData, 16)
/**
* Lightweight version of ShadowTileMapData that only contains data used for rendering the shadow.
*/
struct ShadowRenderView {
/**
* Is either:
* - positive radial distance for point lights.
* - negative distance to light plane (divided by sqrt3) for area lights side projections.
* - zero if disabled.
* Use sign to determine with case we are in.
*/
float clip_distance_inv;
/* Viewport to submit the geometry of this tilemap view to. */
uint viewport_index;
uint _pad0;
uint _pad1;
};
BLI_STATIC_ASSERT_ALIGN(ShadowRenderView, 16)
/**
* Per tilemap data persistent on GPU.
* Kept separately for easier clearing on GPU.
*/
struct ShadowTileMapClip {
/** Clip distances that were used to render the pages. */
@ -1976,6 +2000,7 @@ using ShadowPageCacheBuf = draw::StorageArrayBuffer<uint2, SHADOW_MAX_PAGE, true
using ShadowTileMapDataBuf = draw::StorageVectorBuffer<ShadowTileMapData, SHADOW_MAX_TILEMAP>;
using ShadowTileMapClipBuf = draw::StorageArrayBuffer<ShadowTileMapClip, SHADOW_MAX_TILEMAP, true>;
using ShadowTileDataBuf = draw::StorageArrayBuffer<ShadowTileDataPacked, SHADOW_MAX_TILE, true>;
using ShadowRenderViewBuf = draw::StorageArrayBuffer<ShadowRenderView, SHADOW_VIEW_MAX, true>;
using SurfelBuf = draw::StorageArrayBuffer<Surfel, 64>;
using SurfelRadianceBuf = draw::StorageArrayBuffer<SurfelRadiance, 64>;
using CaptureInfoBuf = draw::StorageBuffer<CaptureInfoData>;

View File

@ -37,6 +37,8 @@ void ShadowTileMap::sync_orthographic(const float4x4 &object_mat_,
}
projection_type = projection_type_;
level = clipmap_level;
light_type = eLightType::LIGHT_SUN;
is_area_side = false;
if (grid_shift == int2(0)) {
/* Only replace shift if it is not already dirty. */
@ -69,7 +71,8 @@ void ShadowTileMap::sync_orthographic(const float4x4 &object_mat_,
1.0);
}
void ShadowTileMap::sync_cubeface(const float4x4 &object_mat_,
void ShadowTileMap::sync_cubeface(eLightType light_type_,
const float4x4 &object_mat_,
float near_,
float far_,
float side_,
@ -84,6 +87,8 @@ void ShadowTileMap::sync_cubeface(const float4x4 &object_mat_,
cubeface = face;
grid_offset = int2(0);
lod_bias = lod_bias_;
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_)) {
set_dirty();
@ -91,6 +96,7 @@ void ShadowTileMap::sync_cubeface(const float4x4 &object_mat_,
clip_near = near_;
clip_far = far_;
area_shift = shift;
half_size = side_;
center_offset = float2(0.0f);
@ -259,25 +265,27 @@ void ShadowPunctual::release_excess_tilemaps()
tilemaps_ = span.take_front(tilemaps_needed_);
}
void ShadowPunctual::compute_projection_boundaries(float light_radius,
void ShadowPunctual::compute_projection_boundaries(eLightType light_type,
float light_radius,
float shadow_radius,
float max_lit_distance,
float &near,
float &far,
float &side)
float &side,
float &back_shift)
{
/**
/*
* In order to make sure we can trace any ray in its entirety using a single tile-map, we have
* to make sure that the tile-map cover all potential occluder that can intersect any ray shot
* in this particular shadow quadrant.
*
* To this end, we shift the tile-map perspective origin behind the light shape and make sure the
* To this end, we inflate the tile-map perspective sides to make sure the
* tile-map frustum starts where the rays cannot go.
*
* We are interesting in finding `I` the new origin and `n` the new near plane distances.
*
* I .... Shifted light center
* /|
* I .... Intersection between tangent and
* /| projection center axis
* / |
* / |
* / |
@ -292,22 +300,22 @@ void ShadowPunctual::compute_projection_boundaries(float light_radius,
* / ... |
* /. |
* / |
* Tangent to light shape .... T\--------------N .... Shifted near plane
* Tangent to light shape .... T\--------------N
* / --\ Beta |
* / -\ |
* / --\ |
* /. --\ |
* / . -\ |
* / . Alpha -O .... Light center
* / . --/ |
* / . --/ |
* / . -/ |
* / . --/ |
* /-------------/------------x .... Desired near plane (inscribed cube)
* / --/ .. |
* / --/ ... |
* / --/ .... |
* / -/ ....|
* / . /-/ |
* Inflated side / . /--- -/ |
* . / . /---- --/ |
* . / /---- . --/ |
* /-------------/------------X .... Desired near plane (inscribed cube)
* /---- --/ .. |
* /---- / --/ ... |
* /---- / --/ .... |
* / -/ ....| .... Shadow radius
* / --/ |
* /--/ |
* F .... Most distant shadow receiver possible.
@ -315,18 +323,36 @@ void ShadowPunctual::compute_projection_boundaries(float light_radius,
* F: The most distant shadowed point at the edge of the 45° cube-face pyramid.
* O: The light origin.
* T: The tangent to the circle of radius `radius` centered at the origin and passing through F.
* I: The shifted light origin.
* I: Intersection between tangent and the projection center axis.
* N: The shifted near plane center.
* X: Intersection between the near plane and the projection center axis.
* Alpha: FOT angle.
* Beta: OTN angle.
*
* TODO(fclem): Explain derivation.
* Note: FTO, ONT and TNI are right angles.
*/
float cos_alpha = shadow_radius / max_lit_distance;
float sin_alpha = sqrt(1.0f - math::square(cos_alpha));
float near_shift = M_SQRT2 * shadow_radius * 0.5f * (sin_alpha - cos_alpha);
float side_shift = M_SQRT2 * shadow_radius * 0.5f * (sin_alpha + cos_alpha);
float origin_shift = M_SQRT2 * shadow_radius / (sin_alpha - cos_alpha);
/* Make near plane to be inside the inscribed cube of the sphere. */
near = max_ff(light_radius, max_lit_distance / 4000.0f) / M_SQRT3;
float min_near = (max_lit_distance / 4000.0f) / M_SQRT3;
if (is_area_light(light_type)) {
/* Make near plane be inside the inscribed cube of the shadow sphere. */
near = max_ff(shadow_radius / M_SQRT3, min_near);
/* Subtract min_near to make the shadow center match the light center if there is no shadow
* tracing required. This avoid light leaking issues near the light plane caused by the
* shadow discard clipping. */
back_shift = (near - min_near);
}
else {
/* Make near plane be inside the inscribed cube of the light sphere. */
near = max_ff(light_radius / M_SQRT3, min_near);
back_shift = 0.0f;
}
far = max_lit_distance;
if (shadow_radius > 1e-5f) {
side = ((side_shift / (origin_shift - near_shift)) * (origin_shift + near));
@ -340,11 +366,9 @@ void ShadowPunctual::end_sync(Light &light, float lod_bias)
{
ShadowTileMapPool &tilemap_pool = shadows_.tilemap_pool;
float side, near, far;
compute_projection_boundaries(light_radius_, shadow_radius_, max_distance_, near, far, side);
/* Shift shadow map origin for area light to avoid clipping nearby geometry. */
float shift = is_area_light(light.type) ? near : 0.0f;
float side, near, far, shift;
compute_projection_boundaries(
light.type, light_radius_, shadow_radius_, max_distance_, near, far, side, shift);
float4x4 obmat_tmp = light.object_mat;
@ -357,15 +381,20 @@ void ShadowPunctual::end_sync(Light &light, float lod_bias)
tilemaps_.append(tilemap_pool.acquire());
}
tilemaps_[Z_NEG]->sync_cubeface(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, lod_bias);
if (tilemaps_needed_ >= 5) {
tilemaps_[X_POS]->sync_cubeface(obmat_tmp, near, far, side, shift, X_POS, lod_bias);
tilemaps_[X_NEG]->sync_cubeface(obmat_tmp, near, far, side, shift, X_NEG, lod_bias);
tilemaps_[Y_POS]->sync_cubeface(obmat_tmp, near, far, side, shift, Y_POS, lod_bias);
tilemaps_[Y_NEG]->sync_cubeface(obmat_tmp, near, far, side, shift, Y_NEG, lod_bias);
tilemaps_[X_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, X_POS, lod_bias);
tilemaps_[X_NEG]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, X_NEG, lod_bias);
tilemaps_[Y_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Y_POS, lod_bias);
tilemaps_[Y_NEG]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Y_NEG, lod_bias);
}
if (tilemaps_needed_ == 6) {
tilemaps_[Z_POS]->sync_cubeface(obmat_tmp, near, far, side, shift, Z_POS, lod_bias);
tilemaps_[Z_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Z_POS, lod_bias);
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
@ -1195,7 +1224,7 @@ void ShadowModule::end_sync()
sub.bind_ssbo("dst_coord_buf", dst_coord_buf_);
sub.bind_ssbo("src_coord_buf", src_coord_buf_);
sub.bind_ssbo("render_map_buf", render_map_buf_);
sub.bind_ssbo("viewport_index_buf", viewport_index_buf_);
sub.bind_ssbo("render_view_buf", render_view_buf_);
sub.bind_ssbo("pages_infos_buf", pages_infos_data_);
sub.bind_image("tilemaps_img", tilemap_pool.tilemap_tx);
sub.dispatch(int3(1, 1, tilemap_pool.tilemaps_data.size()));

View File

@ -103,7 +103,8 @@ struct ShadowTileMap : public ShadowTileMapData {
float lod_bias_,
eShadowProjectionType projection_type_);
void sync_cubeface(const float4x4 &object_mat,
void sync_cubeface(eLightType light_type_,
const float4x4 &object_mat,
float near,
float far,
float side,
@ -252,8 +253,8 @@ class ShadowModule {
StorageArrayBuffer<uint, SHADOW_RENDER_MAP_SIZE, true> src_coord_buf_ = {"src_coord_buf"};
/** Same as dst_coord_buf_ but is not compact. More like a linear texture. */
StorageArrayBuffer<uint, SHADOW_RENDER_MAP_SIZE, true> render_map_buf_ = {"render_map_buf"};
/** View to viewport index mapping. */
StorageArrayBuffer<uint, SHADOW_VIEW_MAX, true> viewport_index_buf_ = {"viewport_index_buf"};
/** View to viewport index mapping and other render-only related data. */
ShadowRenderViewBuf render_view_buf_ = {"render_view_buf"};
int3 dispatch_depth_scan_size_;
float pixel_world_radius_;
@ -453,12 +454,14 @@ class ShadowPunctual : public NonCopyable, NonMovable {
* Make sure that the projection encompass all possible rays that can start in the projection
* quadrant.
*/
void compute_projection_boundaries(float light_radius,
void compute_projection_boundaries(eLightType light_type,
float light_radius,
float shadow_radius,
float max_lit_distance,
float &near,
float &far,
float &side);
float &side,
float &back_shift);
};
class ShadowDirectional : public NonCopyable, NonMovable {

View File

@ -13,7 +13,7 @@ void main()
{
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(viewport_index_buf[drw_view_id]));
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
#endif
init_interface();
@ -57,5 +57,10 @@ void main()
clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0));
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);
}

View File

@ -13,7 +13,7 @@ void main()
{
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(viewport_index_buf[drw_view_id]));
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
#endif
init_interface();
@ -56,4 +56,9 @@ void main()
#ifdef MAT_CLIP_PLANE
clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0));
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
#endif
}

View File

@ -12,7 +12,7 @@ void main()
{
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(viewport_index_buf[drw_view_id]));
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
#endif
init_interface();
@ -39,5 +39,10 @@ void main()
clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0));
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);
}

View File

@ -14,7 +14,7 @@ void main()
{
DRW_VIEW_FROM_RESOURCE_ID;
#ifdef MAT_SHADOW
shadow_viewport_layer_set(int(drw_view_id), int(viewport_index_buf[drw_view_id]));
shadow_viewport_layer_set(int(drw_view_id), int(render_view_buf[drw_view_id].viewport_index));
#endif
init_interface();
@ -51,5 +51,10 @@ void main()
clip_interp.clip_distance = dot(clip_plane.plane, vec4(interp.P, 1.0));
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);
}

View File

@ -103,7 +103,21 @@ void main()
view_index = atomicAdd(statistics_buf.view_needed_count, 1);
if (view_index < SHADOW_VIEW_MAX) {
/* Setup the view. */
viewport_index_buf[view_index] = viewport_index;
render_view_buf[view_index].viewport_index = viewport_index;
/* Clipping setup. */
if (tilemap_data.is_area_side) {
/* Negative for tagging this case. See shadow_clip_vector_get for explanation. */
render_view_buf[view_index].clip_distance_inv = -M_SQRT1_3 / tilemap_data.area_shift;
}
else if (is_point_light(tilemap_data.light_type)) {
/* Clip as a sphere around the clip_near cube. */
render_view_buf[view_index].clip_distance_inv = M_SQRT1_3 / tilemap_data.clip_near;
}
else {
/* Disable local clipping. */
render_view_buf[view_index].clip_distance_inv = 0.0;
}
view_infos_buf[view_index].viewmat = tilemap_data.viewmat;
view_infos_buf[view_index].viewinv = inverse(tilemap_data.viewmat);

View File

@ -161,6 +161,26 @@ void shadow_viewport_layer_set(int view_id, int lod)
# endif
gpu_ViewportIndex = lod;
}
/* In order to support physical clipping, we pass a vector to the fragment shader that then clips
* each fragment using a unit sphere test. This allows to support both point light and area light
* clipping at the same time. */
vec3 shadow_clip_vector_get(vec3 view_position, ShadowRenderView shadow_view)
{
float clip_distance_inv = shadow_view.clip_distance_inv;
if (clip_distance_inv == 0.0) {
/* No clipping. */
return vec3(2.0);
}
if (clip_distance_inv < 0.0) {
/* Area light side projections. Clip using the up axis (which maps to light -Z). */
/* Note: clip_distance_inv should already be scaled by M_SQRT3. */
return vec3(view_position.y * clip_distance_inv);
}
/* Sphere light case. */
return view_position * clip_distance_inv;
}
#endif
#if defined(GPU_FRAGMENT_SHADER) && defined(MAT_SHADOW)

View File

@ -26,6 +26,12 @@ void main()
{
float f_depth = gl_FragCoord.z + fwidth(gl_FragCoord.z);
/* Clip to light shape. */
if (length_squared(shadow_clip.vector) < 1.0) {
discard;
return;
}
#ifdef MAT_TRANSPARENT
init_globals();

View File

@ -231,14 +231,18 @@ GPU_SHADER_CREATE_INFO(eevee_surf_world)
GPU_SHADER_INTERFACE_INFO(eevee_surf_shadow_atomic_iface, "shadow_iface")
.flat(Type::INT, "shadow_view_id");
GPU_SHADER_INTERFACE_INFO(eevee_surf_shadow_clipping_iface, "shadow_clip")
.smooth(Type::VEC3, "vector");
GPU_SHADER_CREATE_INFO(eevee_surf_shadow)
.define("DRW_VIEW_LEN", STRINGIFY(SHADOW_VIEW_MAX))
.define("MAT_SHADOW")
.builtins(BuiltinBits::VIEWPORT_INDEX)
.storage_buf(SHADOW_VIEWPORT_INDEX_BUF_SLOT,
.vertex_out(eevee_surf_shadow_clipping_iface)
.storage_buf(SHADOW_RENDER_VIEW_BUF_SLOT,
Qualifier::READ,
"uint",
"viewport_index_buf[SHADOW_VIEW_MAX]")
"ShadowRenderView",
"render_view_buf[SHADOW_VIEW_MAX]")
.fragment_source("eevee_surf_shadow_frag.glsl")
.additional_info("eevee_global_ubo", "eevee_utility_texture", "eevee_sampling_data");

View File

@ -189,7 +189,7 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tilemap_finalize)
.storage_buf(7, Qualifier::WRITE, SHADOW_PAGE_PACKED, "dst_coord_buf[SHADOW_RENDER_MAP_SIZE]")
.storage_buf(8, Qualifier::WRITE, SHADOW_PAGE_PACKED, "src_coord_buf[SHADOW_RENDER_MAP_SIZE]")
.storage_buf(9, Qualifier::WRITE, SHADOW_PAGE_PACKED, "render_map_buf[SHADOW_RENDER_MAP_SIZE]")
.storage_buf(10, Qualifier::WRITE, "uint", "viewport_index_buf[SHADOW_VIEW_MAX]")
.storage_buf(10, Qualifier::WRITE, "ShadowRenderView", "render_view_buf[SHADOW_VIEW_MAX]")
.storage_buf(11, Qualifier::READ, "ShadowTileMapClip", "tilemaps_clip_buf[]")
/* 12 is the minimum number of storage buf we require. Do not go above this limit. */
.image(0, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_2D, "tilemaps_img")

View File

@ -233,7 +233,8 @@ static void test_eevee_shadow_tag_update()
{
ShadowTileMap tilemap(0 * SHADOW_TILEDATA_PER_TILEMAP);
tilemap.sync_cubeface(float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemap.sync_cubeface(
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemaps_data.append(tilemap);
}
{
@ -1541,7 +1542,8 @@ static void test_eevee_shadow_page_mask_ex(int max_view_per_tilemap)
{
ShadowTileMap tilemap(0);
tilemap.sync_cubeface(float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemap.sync_cubeface(
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemaps_data.append(tilemap);
}