EEVEE-Next: Improve shadow tracing

The goal of this PR is to remove the per face, NDC space
tracing for shadow maps.

This requires custom occluder extrapolation but in return
removes quite a lot of complexity in other areas, namely:
- No more per face transform before the tracing
- No more per face jittering (fix #119565)
- No more frustum padding (increased maximum precision)
- Better use of 32bit precision shadow map
- Fix #121343

This improve softness at relatively low step count (default 6
is better) and reduces light leaking at very low sample
count (sharper). It makes it more intuitive now that
higher sample count is smoother.

Pull Request: https://projects.blender.org/blender/blender/pulls/121317
This commit is contained in:
Clément Foucault
2024-05-07 14:52:47 +02:00
committed by Clément Foucault
parent 18cccafeaf
commit 87d164c56b
26 changed files with 373 additions and 764 deletions

View File

@@ -1237,11 +1237,6 @@ 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)
@@ -1259,9 +1254,10 @@ struct ShadowRenderView {
float clip_distance_inv;
/* Viewport to submit the geometry of this tilemap view to. */
uint viewport_index;
/* Filter radius for this view. */
float filter_radius;
uint _pad1;
/* True if comming from a sun light shadow. */
bool32_t is_directionnal;
/* If directionnal, distance along the negative Z axis of the near clip in view space. */
float clip_near;
};
BLI_STATIC_ASSERT_ALIGN(ShadowRenderView, 16)

View File

@@ -30,19 +30,15 @@ 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) ||
(filter_radius != filter_radius_))
{
if ((projection_type != projection_type_) || (level != clipmap_level)) {
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. */
@@ -82,7 +78,6 @@ 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)) {
@@ -95,13 +90,10 @@ 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_) || (filter_radius != filter_radius_) || (clip_far != far_) ||
(half_size != side_))
{
if ((clip_near != near_) || (clip_far != far_) || (half_size != side_)) {
set_dirty();
}
filter_radius = filter_radius_;
clip_near = near_;
clip_far = far_;
area_shift = shift;
@@ -274,100 +266,15 @@ void ShadowPunctual::release_excess_tilemaps()
}
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 &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 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 .... Intersection between tangent and
* /| projection center axis
* / |
* / |
* / |
* / |
* / |
* / |
* / |
* / |
* / |
* / ....|
* / .... |
* / ... |
* /. |
* / |
* Tangent to light shape .... T\--------------N
* / --\ Beta |
* / -\ |
* / --\ |
* /. --\ |
* / . -\ |
* / . Alpha -O .... Light center
* / . /-/ |
* Inflated side / . /--- -/ |
* . / . /---- --/ |
* . / /---- . --/ |
* /-------------/------------X .... Desired near plane (inscribed cube)
* /---- --/ .. |
* /---- / --/ ... |
* /---- / --/ .... |
* / -/ ....| .... Shadow radius
* / --/ |
* /--/ |
* F .... Most distant shadow receiver possible.
*
* 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: 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.
*
* 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);
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));
}
else {
side = near;
}
near = side = (max_lit_distance / 4000.0f) / M_SQRT3;
back_shift = 0.0f;
}
void ShadowPunctual::end_sync(Light &light, float lod_bias)
@@ -375,8 +282,7 @@ void ShadowPunctual::end_sync(Light &light, float lod_bias)
ShadowTileMapPool &tilemap_pool = shadows_.tilemap_pool;
float side, near, far, shift;
compute_projection_boundaries(
light.type, light_radius_, shadow_radius_, max_distance_, near, far, side, shift);
compute_projection_boundaries(light.type, max_distance_, near, far, side, shift);
float4x4 obmat_tmp = light.object_to_world;
@@ -385,21 +291,20 @@ 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, light.pcf_radius, 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(
light.type, obmat_tmp, near, far, side, shift, X_POS, light.pcf_radius, lod_bias);
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, light.pcf_radius, lod_bias);
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, light.pcf_radius, lod_bias);
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, light.pcf_radius, lod_bias);
light.type, obmat_tmp, near, far, side, shift, Y_NEG, lod_bias);
}
if (tilemaps_needed_ == 6) {
tilemaps_[Z_POS]->sync_cubeface(
light.type, obmat_tmp, near, far, side, shift, Z_POS, light.pcf_radius, lod_bias);
light.type, obmat_tmp, near, far, side, shift, Z_POS, lod_bias);
}
light.tilemap_index = tilemap_pool.tilemaps_data.size();
@@ -544,8 +449,7 @@ 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, light.pcf_radius, SHADOW_PROJECTION_CASCADE);
tilemap->sync_orthographic(object_mat_, level_offset, level, 0.0f, SHADOW_PROJECTION_CASCADE);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
@@ -612,7 +516,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, light.pcf_radius, SHADOW_PROJECTION_CLIPMAP);
object_mat_, level_offset, level, lod_bias, SHADOW_PROJECTION_CLIPMAP);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
shadows_.tilemap_pool.tilemaps_data.append(*tilemap);
@@ -1460,15 +1364,18 @@ void ShadowModule::set_view(View &view, int2 extent)
{
/* Depth is cleared to 0 for TBDR optimization. */
{GPU_LOADACTION_CLEAR, GPU_STOREACTION_DONT_CARE, {0.0f, 0.0f, 0.0f, 0.0f}},
{GPU_LOADACTION_CLEAR, GPU_STOREACTION_DONT_CARE, {1.0f, 1.0f, 1.0f, 1.0f}},
{GPU_LOADACTION_CLEAR,
GPU_STOREACTION_DONT_CARE,
{FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX}},
});
}
else if (shadow_depth_fb_tx_.is_valid()) {
GPU_framebuffer_bind_ex(
render_fb_,
{
{GPU_LOADACTION_CLEAR, GPU_STOREACTION_DONT_CARE, {1.0f, 1.0f, 1.0f, 1.0f}},
});
GPU_framebuffer_bind_ex(render_fb_,
{
{GPU_LOADACTION_CLEAR,
GPU_STOREACTION_DONT_CARE,
{FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX}},
});
}
else {
GPU_framebuffer_bind(render_fb_);

View File

@@ -101,7 +101,6 @@ 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_,
@@ -111,7 +110,6 @@ struct ShadowTileMap : public ShadowTileMapData {
float side,
float shift,
eCubeFace face,
float filter_radius,
float lod_bias_);
void debug_draw() const;
@@ -456,8 +454,6 @@ class ShadowPunctual : public NonCopyable, NonMovable {
* quadrant.
*/
void compute_projection_boundaries(eLightType light_type,
float light_radius,
float shadow_radius,
float max_lit_distance,
float &near,
float &far,

View File

@@ -50,11 +50,10 @@ void thickness_from_shadow_single(uint l_idx,
/* Inverting this bias means we will over estimate the distance. Which removes some artifacts. */
P_offset -= texel_radius * shadow_pcf_offset(lv.L, Ng, pcf_random);
ShadowEvalResult result = shadow_sample(
float occluder_delta = shadow_sample(
is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P_offset);
if (result.light_visibilty == 0.0) {
float hit_distance = result.occluder_distance;
if (occluder_delta > 0.0) {
float hit_distance = abs(occluder_delta);
/* Add back the amount of offset we added to the original position.
* This avoids self shadowing issue. */
hit_distance += (normal_offset + 1.0) * texel_radius;

View File

@@ -14,7 +14,6 @@ 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();
@@ -59,8 +58,10 @@ void main()
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id].clip_distance_inv);
vec3 vs_P = drw_point_world_to_view(interp.P);
ShadowRenderView view = render_view_buf[drw_view_id];
shadow_clip.position = shadow_position_vector_get(vs_P, view);
shadow_clip.vector = shadow_clip_vector_get(vs_P, view.clip_distance_inv);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);

View File

@@ -14,7 +14,6 @@ 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();
@@ -59,7 +58,9 @@ void main()
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id].clip_distance_inv);
vec3 vs_P = drw_point_world_to_view(interp.P);
ShadowRenderView view = render_view_buf[drw_view_id];
shadow_clip.position = shadow_position_vector_get(vs_P, view);
shadow_clip.vector = shadow_clip_vector_get(vs_P, view.clip_distance_inv);
#endif
}

View File

@@ -13,7 +13,6 @@ 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();
@@ -41,8 +40,10 @@ void main()
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id].clip_distance_inv);
vec3 vs_P = drw_point_world_to_view(interp.P);
ShadowRenderView view = render_view_buf[drw_view_id];
shadow_clip.position = shadow_position_vector_get(vs_P, view);
shadow_clip.vector = shadow_clip_vector_get(vs_P, view.clip_distance_inv);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);

View File

@@ -15,7 +15,6 @@ 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();
@@ -53,8 +52,10 @@ void main()
#endif
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id].clip_distance_inv);
vec3 vs_P = drw_point_world_to_view(interp.P);
ShadowRenderView view = render_view_buf[drw_view_id];
shadow_clip.position = shadow_position_vector_get(vs_P, view);
shadow_clip.vector = shadow_clip_vector_get(vs_P, view.clip_distance_inv);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);

View File

@@ -181,17 +181,16 @@ void light_eval_single(uint l_idx,
float shadow = 1.0;
if (light.tilemap_index != LIGHT_NO_SHADOW) {
ShadowEvalResult result = shadow_eval(light,
is_directional,
is_transmission,
is_translucent_with_thickness,
thickness,
P,
Ng,
lv.L,
ray_count,
ray_step_count);
shadow = result.light_visibilty;
shadow = shadow_eval(light,
is_directional,
is_transmission,
is_translucent_with_thickness,
thickness,
P,
Ng,
lv.L,
ray_count,
ray_step_count);
}
if (is_translucent_with_thickness) {

View File

@@ -18,6 +18,11 @@
/** Control the scaling of the tile-map splat. */
const float pixel_scale = 4.0;
ShadowSamplingTile shadow_tile_data_get(usampler2D tilemaps_tx, ShadowCoordinates coord)
{
return shadow_tile_load(tilemaps_tx, coord.tilemap_tile, coord.tilemap_index);
}
vec3 debug_random_color(ivec2 v)
{
float r = interlieved_gradient_noise(vec2(v), 0.0, 0.0);
@@ -69,19 +74,24 @@ vec3 debug_tile_state_color(eLightType type, ShadowSamplingTile tile)
SHADOW_TILEMAP_LOD));
}
ShadowSampleParams debug_shadow_sample_get(vec3 P, LightData light)
ShadowCoordinates debug_coord_get(vec3 P, LightData light)
{
if (is_sun_light(light.type)) {
return shadow_directional_sample_params_get(shadow_tilemaps_tx, light, P);
vec3 lP = light_world_to_local(light, P);
return shadow_directional_coordinates(light, lP);
}
else {
return shadow_punctual_sample_params_get(light, P);
vec3 lP = light_world_to_local_point(light, P);
int face_id = shadow_punctual_face_index_get(lP);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
return shadow_punctual_coordinates(light, lP, face_id);
}
}
ShadowSamplingTile debug_tile_get(vec3 P, LightData light)
{
return shadow_tile_data_get(shadow_tilemaps_tx, debug_shadow_sample_get(P, light));
ShadowCoordinates coord = debug_coord_get(P, light);
return shadow_tile_data_get(shadow_tilemaps_tx, coord);
}
LightData debug_light_get()
@@ -114,9 +124,9 @@ bool debug_tilemaps(vec3 P, LightData light)
if ((px.y < SHADOW_TILEMAP_RES) && (tilemap_index <= light_tilemap_max_get(light))) {
#if 1
/* Debug values in the tilemap_tx. */
ivec2 tilemap_texel = shadow_tile_coord_in_atlas(px, tilemap_index);
uvec2 tilemap_texel = shadow_tile_coord_in_atlas(uvec2(px), tilemap_index);
ShadowSamplingTile tile = shadow_sampling_tile_unpack(
texelFetch(shadow_tilemaps_tx, tilemap_texel, 0).x);
texelFetch(shadow_tilemaps_tx, ivec2(tilemap_texel), 0).x);
/* Leave 1 px border between tile-maps. */
if (!any(equal(ivec2(gl_FragCoord.xy) % (SHADOW_TILEMAP_RES * debug_tile_size_px), ivec2(0))))
{
@@ -155,9 +165,9 @@ void debug_tile_state(vec3 P, LightData light)
void debug_atlas_values(vec3 P, LightData light)
{
ShadowSampleParams samp = debug_shadow_sample_get(P, light);
float depth = shadow_read_depth(shadow_atlas_tx, shadow_tilemaps_tx, samp);
out_color_add = vec4(float3(depth), 0.0);
ShadowCoordinates coord = debug_coord_get(P, light);
float depth = shadow_read_depth(shadow_atlas_tx, shadow_tilemaps_tx, coord);
out_color_add = vec4((depth == -1) ? vec3(1.0, 0.0, 0.0) : float3(1.0 / depth), 0.0);
out_color_mul = vec4(0.5);
}
@@ -170,18 +180,7 @@ void debug_random_tile_color(vec3 P, LightData light)
void debug_random_tilemap_color(vec3 P, LightData light)
{
ShadowCoordinates coord;
if (is_sun_light(light.type)) {
vec3 lP = light_world_to_local(light, P);
coord = shadow_directional_coordinates(light, lP);
}
else {
vec3 lP = light_world_to_local(light, P - light_position_get(light));
int face_id = shadow_punctual_face_index_get(lP);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
coord = shadow_punctual_coordinates(light, lP, face_id);
}
ShadowCoordinates coord = debug_coord_get(P, light);
out_color_add = vec4(debug_random_color(ivec2(coord.tilemap_index)), 0) * 0.5;
out_color_mul = vec4(0.5);
}

View File

@@ -14,278 +14,74 @@
# define SHADOW_ATLAS_TYPE usampler2DArray
#endif
struct ShadowSampleParams {
vec3 lP;
vec3 uv;
int tilemap_index;
float z_range;
};
ShadowSamplingTile shadow_tile_data_get(usampler2D tilemaps_tx, ShadowSampleParams params)
{
/* Prevent out of bound access. Assumes the input is already non negative. */
vec2 tilemap_uv = min(params.uv.xy, vec2(0.99999));
ivec2 texel_coord = ivec2(tilemap_uv * float(SHADOW_MAP_MAX_RES));
/* Using bitwise ops is way faster than integer ops. */
const int page_shift = SHADOW_PAGE_LOD;
ivec2 tile_coord = texel_coord >> page_shift;
return shadow_tile_load(tilemaps_tx, tile_coord, params.tilemap_index);
}
float shadow_read_depth(SHADOW_ATLAS_TYPE atlas_tx,
usampler2D tilemaps_tx,
ShadowSampleParams params)
ShadowCoordinates coord)
{
/* Prevent out of bound access. Assumes the input is already non negative. */
vec2 tilemap_uv = min(params.uv.xy, vec2(0.99999));
ivec2 texel_coord = ivec2(tilemap_uv * float(SHADOW_MAP_MAX_RES));
/* Using bitwise ops is way faster than integer ops. */
const int page_shift = SHADOW_PAGE_LOD;
ivec2 tile_coord = texel_coord >> page_shift;
ShadowSamplingTile tile = shadow_tile_load(tilemaps_tx, tile_coord, params.tilemap_index);
ShadowSamplingTile tile = shadow_tile_load(tilemaps_tx, coord.tilemap_tile, coord.tilemap_index);
if (!tile.is_valid) {
return -1.0;
}
/* Using bitwise ops is way faster than integer ops. */
const uint page_shift = uint(SHADOW_PAGE_LOD);
const uint page_mask = ~(0xFFFFFFFFu << uint(SHADOW_PAGE_LOD));
int page_mask = ~(0xFFFFFFFF << (SHADOW_PAGE_LOD + int(tile.lod)));
ivec2 texel_page = (texel_coord & page_mask) >> int(tile.lod);
ivec3 texel = ivec3((ivec2(tile.page.xy) << page_shift) | texel_page, tile.page.z);
uvec2 texel = coord.tilemap_texel;
/* Shift LOD0 pixels so that they get wrapped at the right position for the given LOD. */
texel += uvec2(tile.lod_offset << SHADOW_PAGE_LOD);
/* Scale to LOD pixels (merge LOD0 pixels together) then mask to get pixel in page. */
uvec2 texel_page = (texel >> tile.lod) & page_mask;
texel = (uvec2(tile.page.xy) << page_shift) | texel_page;
return uintBitsToFloat(texelFetch(atlas_tx, texel, 0).r);
return uintBitsToFloat(texelFetch(atlas_tx, ivec3(ivec2(texel), tile.page.z), 0).r);
}
struct ShadowEvalResult {
/* Visibility of the light. */
float light_visibilty;
/* Average occluder distance. In world space linear distance. */
float occluder_distance;
};
/* ---------------------------------------------------------------------- */
/** \name Shadow Sampling Functions
* \{ */
mat4x4 shadow_projection_perspective(float side, float near_clip, float far_clip)
{
float z_delta = far_clip - near_clip;
mat4x4 mat = mat4x4(1.0);
mat[0][0] = near_clip / side;
mat[1][1] = near_clip / side;
mat[2][0] = 0.0;
mat[2][1] = 0.0;
mat[2][2] = -(far_clip + near_clip) / z_delta;
mat[2][3] = -1.0;
mat[3][2] = (-2.0 * near_clip * far_clip) / z_delta;
mat[3][3] = 0.0;
return mat;
}
mat4x4 shadow_projection_perspective_inverse(float side, float near_clip, float far_clip)
{
float z_delta = far_clip - near_clip;
float d = 2.0 * near_clip * far_clip;
mat4x4 mat = mat4x4(1.0);
mat[0][0] = side / near_clip;
mat[1][1] = side / near_clip;
mat[2][0] = 0.0;
mat[2][1] = 0.0;
mat[2][2] = 0.0;
mat[2][3] = (near_clip - far_clip) / d;
mat[3][2] = -1.0;
mat[3][3] = (near_clip + far_clip) / d;
return mat;
}
/**
* Convert occluder distance in shadow space to world space distance.
* Assuming the occluder is above the shading point in direction to the shadow projection center.
*/
float shadow_linear_occluder_distance(LightData light,
const bool is_directional,
vec3 lP,
float occluder)
{
float near = orderedIntBitsToFloat(light.clip_near);
float far = orderedIntBitsToFloat(light.clip_far);
float occluder_z = (is_directional) ? (occluder * (far - near) + near) :
((near * far) / (occluder * (near - far) + far));
float receiver_z = (is_directional) ? -lP.z : reduce_max(abs(lP));
if (!is_directional) {
float lP_len = length(lP);
return lP_len - lP_len * (occluder_z / receiver_z);
}
return receiver_z - occluder_z;
}
mat4 shadow_punctual_projection_perspective(LightData light)
{
/* Face Local (View) Space > Clip Space. */
float clip_far = intBitsToFloat(light.clip_far);
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light_local_data_get(light).clip_side;
return shadow_projection_perspective(clip_side, clip_near, clip_far);
}
mat4 shadow_punctual_projection_perspective_inverse(LightData light)
{
/* Face Local (View) Space > Clip Space. */
float clip_far = intBitsToFloat(light.clip_far);
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light_local_data_get(light).clip_side;
return shadow_projection_perspective_inverse(clip_side, clip_near, clip_far);
}
vec3 shadow_punctual_reconstruct_position(ShadowSampleParams params,
mat4 wininv,
LightData light,
vec3 uvw)
{
vec3 clip_P = uvw * 2.0 - 1.0;
vec3 lP = project_point(wininv, clip_P);
int face_id = params.tilemap_index - light.tilemap_index;
lP = shadow_punctual_face_local_to_local_position(face_id, lP);
return transform_point(light.object_to_world, lP);
}
ShadowSampleParams shadow_punctual_sample_params_get(LightData light, vec3 P)
float shadow_punctual_sample_get(SHADOW_ATLAS_TYPE atlas_tx,
usampler2D tilemaps_tx,
LightData light,
vec3 P)
{
vec3 lP = transform_point_inversed(light.object_to_world, P);
int face_id = shadow_punctual_face_index_get(lP);
/* Local Light Space > Face Local (View) Space. */
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
mat4 winmat = shadow_punctual_projection_perspective(light);
vec3 clip_P = project_point(winmat, lP);
/* Clip Space > UV Space. */
vec3 uv_P = saturate(clip_P * 0.5 + 0.5);
ShadowCoordinates coord = shadow_punctual_coordinates(light, lP, face_id);
ShadowSampleParams result;
result.lP = lP;
result.uv = uv_P;
result.tilemap_index = light.tilemap_index + face_id;
result.z_range = 1.0;
return result;
float radial_dist = shadow_read_depth(atlas_tx, tilemaps_tx, coord);
if (radial_dist == -1.0) {
return 1e10;
}
float receiver_dist = length(lP);
float occluder_dist = radial_dist;
return receiver_dist - occluder_dist;
}
ShadowEvalResult shadow_punctual_sample_get(SHADOW_ATLAS_TYPE atlas_tx,
usampler2D tilemaps_tx,
LightData light,
vec3 P)
float shadow_directional_sample_get(SHADOW_ATLAS_TYPE atlas_tx,
usampler2D tilemaps_tx,
LightData light,
vec3 P)
{
ShadowSampleParams params = shadow_punctual_sample_params_get(light, P);
float depth = shadow_read_depth(atlas_tx, tilemaps_tx, params);
ShadowEvalResult result;
result.light_visibilty = float(params.uv.z < depth);
result.occluder_distance = shadow_linear_occluder_distance(light, false, params.lP, depth);
return result;
}
struct ShadowDirectionalSampleInfo {
float clip_near;
float clip_far;
int level_relative;
int lod_relative;
ivec2 clipmap_offset;
vec2 clipmap_origin;
};
ShadowDirectionalSampleInfo shadow_directional_sample_info_get(LightData light, vec3 lP)
{
ShadowDirectionalSampleInfo info;
info.clip_near = orderedIntBitsToFloat(light.clip_near);
info.clip_far = orderedIntBitsToFloat(light.clip_far);
int level = shadow_directional_level(light, lP - light_position_get(light));
/* This difference needs to be less than 32 for the later shift to be valid.
* This is ensured by ShadowDirectional::clipmap_level_range(). */
info.level_relative = level - light_sun_data_get(light).clipmap_lod_min;
info.lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light_sun_data_get(light).clipmap_lod_min :
level;
info.clipmap_offset = shadow_decompress_grid_offset(
light.type,
light_sun_data_get(light).clipmap_base_offset_neg,
light_sun_data_get(light).clipmap_base_offset_pos,
info.level_relative);
info.clipmap_origin = light_sun_data_get(light).clipmap_origin;
return info;
}
vec3 shadow_directional_reconstruct_position(ShadowSampleParams params, LightData light, vec3 uvw)
{
ShadowDirectionalSampleInfo info = shadow_directional_sample_info_get(light, params.lP);
vec2 tilemap_uv = uvw.xy;
tilemap_uv += vec2(info.clipmap_offset) / float(SHADOW_TILEMAP_RES);
vec2 clipmap_pos = (tilemap_uv - 0.5) / exp2(-float(info.lod_relative));
vec3 lP;
lP.xy = clipmap_pos + info.clipmap_origin;
lP.z = (params.uv.z + info.clip_near) * -1.0;
return transform_direction_transposed(light.object_to_world, lP);
}
ShadowSampleParams shadow_directional_sample_params_get(usampler2D tilemaps_tx,
LightData light,
vec3 P)
{
vec3 lP = transform_direction(light.object_to_world, P);
ShadowDirectionalSampleInfo info = shadow_directional_sample_info_get(light, lP);
vec3 lP = transform_direction_transposed(light.object_to_world, P);
ShadowCoordinates coord = shadow_directional_coordinates(light, lP);
/* Assumed to be non-null. */
float z_range = info.clip_far - info.clip_near;
float dist_to_near_plane = -lP.z - info.clip_near;
vec2 clipmap_pos = lP.xy - info.clipmap_origin;
vec2 tilemap_uv = clipmap_pos * exp2(-float(info.lod_relative)) + 0.5;
/* Translate tilemap UVs to its origin. */
tilemap_uv -= vec2(info.clipmap_offset) / float(SHADOW_TILEMAP_RES);
/* Clamp to avoid out of tilemap access. */
tilemap_uv = saturate(tilemap_uv);
ShadowSampleParams result;
result.lP = lP;
result.uv = vec3(tilemap_uv, dist_to_near_plane);
result.tilemap_index = light.tilemap_index + info.level_relative;
result.z_range = z_range;
return result;
float depth = shadow_read_depth(atlas_tx, tilemaps_tx, coord);
if (depth == -1.0) {
return 1e10;
}
/* Use increasing distance from the light. */
float receiver_dist = -lP.z - orderedIntBitsToFloat(light.clip_near);
float occluder_dist = depth;
return receiver_dist - occluder_dist;
}
ShadowEvalResult shadow_directional_sample_get(SHADOW_ATLAS_TYPE atlas_tx,
usampler2D tilemaps_tx,
LightData light,
vec3 P)
{
ShadowSampleParams params = shadow_directional_sample_params_get(tilemaps_tx, light, P);
float depth = shadow_read_depth(atlas_tx, tilemaps_tx, params);
ShadowEvalResult result;
result.light_visibilty = float(params.uv.z < depth * params.z_range);
result.occluder_distance = shadow_linear_occluder_distance(light, true, params.lP, depth);
return result;
}
ShadowEvalResult shadow_sample(const bool is_directional,
SHADOW_ATLAS_TYPE atlas_tx,
usampler2D tilemaps_tx,
LightData light,
vec3 P)
float shadow_sample(const bool is_directional,
SHADOW_ATLAS_TYPE atlas_tx,
usampler2D tilemaps_tx,
LightData light,
vec3 P)
{
if (is_directional) {
return shadow_directional_sample_get(atlas_tx, tilemaps_tx, light, P);

View File

@@ -17,6 +17,5 @@ void main()
uvec3 page_co = shadow_page_unpack(page_packed);
page_co.xy = page_co.xy * SHADOW_PAGE_RES + gl_GlobalInvocationID.xy;
/* Clear to FLT_MAX instead of 1 so the far plane doesn't cast shadows onto farther objects. */
imageStoreFast(shadow_atlas_img, ivec3(page_co), uvec4(floatBitsToUint(FLT_MAX)));
}

View File

@@ -22,7 +22,7 @@ shared uint levels_rendered;
int shadow_tile_offset_lds(ivec2 tile, int lod)
{
return shadow_tile_offset(tile, 0, lod);
return shadow_tile_offset(uvec2(tile), 0, lod);
}
/* Deactivate threads that are not part of this LOD. Will only let pass threads which tile
@@ -46,7 +46,7 @@ void main()
* main memory the usage bit. */
for (int lod = 0; lod <= SHADOW_TILEMAP_LOD; lod++) {
if (thread_mask(tile_co, lod)) {
int tile_offset = shadow_tile_offset(tile_co, tilemap.tiles_index, lod);
int tile_offset = shadow_tile_offset(uvec2(tile_co), tilemap.tiles_index, lod);
ShadowTileDataPacked tile_data = tiles_buf[tile_offset];
if ((tile_data & SHADOW_IS_USED) == 0) {
@@ -158,7 +158,7 @@ void main()
if (thread_mask(tile_co, lod)) {
int tile_lds = shadow_tile_offset_lds(tile_co, lod);
if ((tiles_local[tile_lds] & SHADOW_TILE_AMENDED) != 0) {
int tile_offset = shadow_tile_offset(tile_co, tilemap.tiles_index, lod);
int tile_offset = shadow_tile_offset(uvec2(tile_co), tilemap.tiles_index, lod);
/* Note that we only flush the visibility so that cached pages can be reused. */
if ((tiles_local[tile_lds] & SHADOW_TILE_MASKED) != 0) {
tiles_buf[tile_offset] &= ~SHADOW_IS_USED;

View File

@@ -95,7 +95,7 @@ void main()
for (int lod = 0; lod <= SHADOW_TILEMAP_LOD; lod++, box_min >>= 1, box_max >>= 1) {
for (int y = box_min.y; y <= box_max.y; y++) {
for (int x = box_min.x; x <= box_max.x; x++) {
int tile_index = shadow_tile_offset(ivec2(x, y), tilemap.tiles_index, lod);
int tile_index = shadow_tile_offset(uvec2(x, y), tilemap.tiles_index, lod);
atomicOr(tiles_buf[tile_index], uint(SHADOW_DO_UPDATE));
}
}

View File

@@ -15,13 +15,13 @@
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
void shadow_tag_usage_tile(LightData light, ivec2 tile_co, int lod, int tilemap_index)
void shadow_tag_usage_tile(LightData light, uvec2 tile_co, int lod, int tilemap_index)
{
if (tilemap_index > light_tilemap_max_get(light)) {
return;
}
tile_co >>= lod;
tile_co >>= uint(lod);
int tile_index = shadow_tile_offset(tile_co, tilemaps_buf[tilemap_index].tiles_index, lod);
atomicOr(tiles_buf[tile_index], uint(SHADOW_IS_USED));
}
@@ -40,7 +40,7 @@ void shadow_tag_usage_tilemap_directional_at_level(uint l_idx, vec3 P, int level
level, light_sun_data_get(light).clipmap_lod_min, light_sun_data_get(light).clipmap_lod_max);
ShadowCoordinates coord = shadow_directional_coordinates_at_level(light, lP, level);
shadow_tag_usage_tile(light, coord.tile_coord, 0, coord.tilemap_index);
shadow_tag_usage_tile(light, coord.tilemap_tile, 0, coord.tilemap_index);
}
void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radius, int lod_bias)
@@ -57,7 +57,7 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
if (radius == 0.0) {
int level = shadow_directional_level(light, lP - light_position_get(light));
ShadowCoordinates coord = shadow_directional_coordinates_at_level(light, lP, level);
shadow_tag_usage_tile(light, coord.tile_coord, 0, coord.tilemap_index);
shadow_tag_usage_tile(light, coord.tilemap_tile, 0, coord.tilemap_index);
}
else {
vec3 start_lP = light_world_to_local(light, P - V * radius);
@@ -71,9 +71,9 @@ void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radi
ShadowCoordinates coord_max = shadow_directional_coordinates_at_level(
light, lP + vec3(radius, radius, 0.0), level);
for (int x = coord_min.tile_coord.x; x <= coord_max.tile_coord.x; x++) {
for (int y = coord_min.tile_coord.y; y <= coord_max.tile_coord.y; y++) {
shadow_tag_usage_tile(light, ivec2(x, y), 0, coord_min.tilemap_index);
for (uint x = coord_min.tilemap_tile.x; x <= coord_max.tilemap_tile.x; x++) {
for (uint y = coord_min.tilemap_tile.y; y <= coord_max.tilemap_tile.y; y++) {
shadow_tag_usage_tile(light, uvec2(x, y), 0, coord_min.tilemap_index);
}
}
}
@@ -121,7 +121,7 @@ void shadow_tag_usage_tilemap_punctual(uint l_idx, vec3 P, float radius, int lod
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);
shadow_tag_usage_tile(light, coord.tile_coord, lod, coord.tilemap_index);
shadow_tag_usage_tile(light, coord.tilemap_tile, lod, coord.tilemap_index);
}
else {
uint faces = 0u;
@@ -146,9 +146,9 @@ void shadow_tag_usage_tilemap_punctual(uint l_idx, vec3 P, float radius, int lod
ShadowCoordinates coord_min = shadow_punctual_coordinates(light, _lP - offset, face_id);
ShadowCoordinates coord_max = shadow_punctual_coordinates(light, _lP + offset, face_id);
for (int x = coord_min.tile_coord.x; x <= coord_max.tile_coord.x; x++) {
for (int y = coord_min.tile_coord.y; y <= coord_max.tile_coord.y; y++) {
shadow_tag_usage_tile(light, ivec2(x, y), lod, tilemap_index);
for (uint x = coord_min.tilemap_tile.x; x <= coord_max.tilemap_tile.x; x++) {
for (uint y = coord_min.tilemap_tile.y; y <= coord_max.tilemap_tile.y; y++) {
shadow_tag_usage_tile(light, uvec2(x, y), lod, tilemap_index);
}
}
}

View File

@@ -130,26 +130,26 @@ void main()
lP = vec3(1e-5, 1e-5, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(-1e-5, -1e-5, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2((SHADOW_TILEMAP_RES / 2) - 1));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2((SHADOW_TILEMAP_RES / 2) - 1));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(-0.5, -0.5, 0.0); /* Min of first LOD. */
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(0));
EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(0));
// EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
lP = vec3(0.5, 0.5, 0.0); /* Max of first LOD. */
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES - 1));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES), 1e-3);
/* Test clip-map level selection. */
@@ -161,26 +161,26 @@ void main()
lP = vec3(2.00001, 2.00001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(1.50001, 1.50001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 1);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
lP = vec3(1.00001, 1.00001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 2);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
lP = vec3(-0.0001, -0.0001, 0.0); /* Out of bounds. */
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 2);
EXPECT_EQ(coords.tile_coord, ivec2(0));
EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(0));
// EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
/* Test clip-map offset. */
@@ -188,16 +188,16 @@ void main()
lP = vec3(2.0001, 0.0001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, -1));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, -1));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, 0));
/* Test clip-map negative offsets. */
@@ -205,16 +205,16 @@ void main()
lP = vec3(-2.0001, -0.0001, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 1));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 1));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 0));
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 0));
}
TEST(eevee_shadow, DirectionalCascadeCoordinates)
@@ -249,34 +249,31 @@ void main()
lP = vec3(1e-8, 1e-8, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 1);
EXPECT_EQ(coords.lod_relative, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(lod_half_size * narrowing - 1e-5, 1e-8, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 1);
EXPECT_EQ(coords.lod_relative, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1, SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(float(SHADOW_TILEMAP_RES) - 0.5, SHADOW_TILEMAP_RES / 2), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES - 1, SHADOW_TILEMAP_RES / 2));
// EXPECT_NEAR(coords.uv, vec2(float(SHADOW_TILEMAP_RES) - 0.5, SHADOW_TILEMAP_RES / 2), 1e-3);
lP = vec3(lod_half_size + 1e-5, 1e-5, 0.0);
coords = shadow_directional_coordinates(light, lP);
EXPECT_EQ(coords.tilemap_index, 2);
EXPECT_EQ(coords.lod_relative, 0);
EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1, SHADOW_TILEMAP_RES / 2));
EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES, SHADOW_TILEMAP_RES / 2), 1e-3);
EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES - 1, SHADOW_TILEMAP_RES / 2));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES, SHADOW_TILEMAP_RES / 2), 1e-3);
// lP = vec3(-0.5, -0.5, 0.0); /* Min of first LOD. */
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 0);
// EXPECT_EQ(coords.tile_coord, ivec2(0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(0));
// EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
// lP = vec3(0.5, 0.5, 0.0); /* Max of first LOD. */
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 0);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES - 1));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES - 1));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES), 1e-3);
/* Test clip-map level selection. */
@@ -289,25 +286,25 @@ void main()
// lP = vec3(2.00001, 2.00001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 0);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 2), 1e-3);
// lP = vec3(1.50001, 1.50001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 1);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
// lP = vec3(1.00001, 1.00001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 2);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 4));
// EXPECT_NEAR(coords.uv, vec2(SHADOW_TILEMAP_RES / 4), 1e-3);
// lP = vec3(-0.0001, -0.0001, 0.0); /* Out of bounds. */
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tilemap_index, 2);
// EXPECT_EQ(coords.tile_coord, ivec2(0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(0));
// EXPECT_NEAR(coords.uv, vec2(0), 1e-3);
/* Test clip-map offset. */
@@ -316,16 +313,16 @@ void main()
// lP = vec3(2.0001, 0.0001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, -1));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, -1));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2) + ivec2(1, 0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2) + uvec2(1, 0));
/* Test clip-map negative offsets. */
@@ -333,15 +330,15 @@ void main()
// lP = vec3(-2.0001, -0.0001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 1));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 1));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 0));
// coords = shadow_directional_coordinates(light, lP);
// EXPECT_EQ(coords.tile_coord, ivec2(SHADOW_TILEMAP_RES / 2 - 1) + ivec2(-1, 0));
// EXPECT_EQ(coords.tilemap_tile, uvec2(SHADOW_TILEMAP_RES / 2 - 1) + uvec2(-1, 0));
}
}

View File

@@ -44,9 +44,9 @@ void main()
/* Iterate in reverse. */
for (int lod = lod_max; lod >= 0; lod--) {
int tilemap_index = light.tilemap_index + lod;
ivec2 atlas_texel = shadow_tile_coord_in_atlas(tile_co, tilemap_index);
uvec2 atlas_texel = shadow_tile_coord_in_atlas(uvec2(tile_co), tilemap_index);
ShadowSamplingTilePacked tile_packed = imageLoad(tilemaps_img, atlas_texel).x;
ShadowSamplingTilePacked tile_packed = imageLoad(tilemaps_img, ivec2(atlas_texel)).x;
ShadowSamplingTile tile = shadow_sampling_tile_unpack(tile_packed);
if (lod != lod_max && !tile.is_valid) {
@@ -81,7 +81,7 @@ void main()
tile_prev_packed = shadow_sampling_tile_pack(tile_prev);
/* Replace the missing page with the one from the lower LOD. */
imageStore(tilemaps_img, atlas_texel, uvec4(tile_prev_packed));
imageStore(tilemaps_img, ivec2(atlas_texel), uvec4(tile_prev_packed));
/* Push this amended tile to the local tiles. */
tile_packed = tile_prev_packed;
tile.is_valid = true;

View File

@@ -51,7 +51,7 @@ void main()
int tilemap_index = int(gl_GlobalInvocationID.z);
ivec2 tile_co = ivec2(gl_GlobalInvocationID.xy);
ivec2 atlas_texel = shadow_tile_coord_in_atlas(tile_co, tilemap_index);
uvec2 atlas_texel = shadow_tile_coord_in_atlas(uvec2(tile_co), tilemap_index);
ShadowTileMapData tilemap_data = tilemaps_buf[tilemap_index];
bool is_cubemap = (tilemap_data.projection_type == SHADOW_PROJECTION_CUBEFACE);
@@ -64,7 +64,7 @@ void main()
* Add one render view per LOD that has tiles to be rendered. */
for (int lod = lod_max; lod >= 0; lod--) {
ivec2 tile_co_lod = tile_co >> lod;
int tile_index = shadow_tile_offset(tile_co_lod, tilemap_data.tiles_index, lod);
int tile_index = shadow_tile_offset(uvec2(tile_co_lod), tilemap_data.tiles_index, lod);
ShadowTileData tile = shadow_tile_unpack(tiles_buf[tile_index]);
@@ -103,26 +103,6 @@ void main()
view_index = atomicAdd(statistics_buf.view_needed_count, 1);
if (view_index < SHADOW_VIEW_MAX) {
/* 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. */
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);
@@ -153,6 +133,23 @@ void main()
view_infos_buf[view_index].winmat = winmat;
view_infos_buf[view_index].wininv = inverse(winmat);
render_view_buf[view_index].viewport_index = viewport_index;
render_view_buf[view_index].is_directionnal = !is_cubemap;
render_view_buf[view_index].clip_near = clip_near;
/* 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;
}
}
}
}
@@ -205,7 +202,7 @@ void main()
ShadowTileData tile_data = shadow_tile_unpack(tile_packed);
ShadowSamplingTile tile_sampling = shadow_sampling_tile_create(tile_data, valid_lod);
ShadowSamplingTilePacked tile_sampling_packed = shadow_sampling_tile_pack(tile_sampling);
imageStore(tilemaps_img, atlas_texel, uvec4(tile_sampling_packed));
imageStore(tilemaps_img, ivec2(atlas_texel), uvec4(tile_sampling_packed));
if (all(equal(gl_GlobalInvocationID, uvec3(0)))) {
/* Clamp it as it can underflow if there is too much tile present on screen. */

View File

@@ -87,7 +87,7 @@ void main()
for (int lod = 0; lod <= lod_max; lod++, lod_size >>= 1u) {
bool thread_active = all(lessThan(tile_co, ivec2(lod_size)));
ShadowTileDataPacked tile = 0;
int tile_load = shadow_tile_offset(tile_wrapped, tilemap.tiles_index, lod);
int tile_load = shadow_tile_offset(uvec2(tile_wrapped), tilemap.tiles_index, lod);
if (thread_active) {
tile = init_tile_data(tiles_buf[tile_load], do_update);
}
@@ -96,7 +96,7 @@ void main()
barrier();
if (thread_active) {
int tile_store = shadow_tile_offset(tile_co, tilemap.tiles_index, lod);
int tile_store = shadow_tile_offset(uvec2(tile_co), tilemap.tiles_index, lod);
if ((tile_load != tile_store) && flag_test(tile, SHADOW_IS_CACHED)) {
/* Inlining of shadow_page_cache_update_tile_ref to avoid buffer dependencies. */
pages_cached_buf[shadow_tile_unpack(tile).cache_index].y = tile_store;

View File

@@ -15,19 +15,19 @@ int shadow_tile_index(ivec2 tile)
return tile.x + tile.y * SHADOW_TILEMAP_RES;
}
ivec2 shadow_tile_coord(int tile_index)
uvec2 shadow_tile_coord(int tile_index)
{
return ivec2(tile_index % SHADOW_TILEMAP_RES, tile_index / SHADOW_TILEMAP_RES);
return uvec2(tile_index % SHADOW_TILEMAP_RES, tile_index / SHADOW_TILEMAP_RES);
}
/* Return bottom left pixel position of the tile-map inside the tile-map atlas. */
ivec2 shadow_tilemap_start(int tilemap_index)
uvec2 shadow_tilemap_start(int tilemap_index)
{
return SHADOW_TILEMAP_RES *
ivec2(tilemap_index % SHADOW_TILEMAP_PER_ROW, tilemap_index / SHADOW_TILEMAP_PER_ROW);
uvec2(tilemap_index % SHADOW_TILEMAP_PER_ROW, tilemap_index / SHADOW_TILEMAP_PER_ROW);
}
ivec2 shadow_tile_coord_in_atlas(ivec2 tile, int tilemap_index)
uvec2 shadow_tile_coord_in_atlas(uvec2 tile, int tilemap_index)
{
return shadow_tilemap_start(tilemap_index) + tile;
}
@@ -36,7 +36,7 @@ ivec2 shadow_tile_coord_in_atlas(ivec2 tile, int tilemap_index)
* Return tile index inside `tiles_buf` for a given tile coordinate inside a specific LOD.
* `tiles_index` should be `ShadowTileMapData.tiles_index`.
*/
int shadow_tile_offset(ivec2 tile, int tiles_index, int lod)
int shadow_tile_offset(uvec2 tile, int tiles_index, int lod)
{
#if SHADOW_TILEMAP_LOD > 5
# error This needs to be adjusted
@@ -54,34 +54,35 @@ int shadow_tile_offset(ivec2 tile, int tiles_index, int lod)
const int lod4_size = lod4_width * lod4_width;
const int lod5_size = lod5_width * lod5_width;
/* TODO(fclem): Convert everything to uint. */
int offset = tiles_index;
switch (lod) {
case 5:
offset += lod0_size + lod1_size + lod2_size + lod3_size + lod4_size;
offset += tile.y * lod5_width;
offset += int(tile.y) * lod5_width;
break;
case 4:
offset += lod0_size + lod1_size + lod2_size + lod3_size;
offset += tile.y * lod4_width;
offset += int(tile.y) * lod4_width;
break;
case 3:
offset += lod0_size + lod1_size + lod2_size;
offset += tile.y * lod3_width;
offset += int(tile.y) * lod3_width;
break;
case 2:
offset += lod0_size + lod1_size;
offset += tile.y * lod2_width;
offset += int(tile.y) * lod2_width;
break;
case 1:
offset += lod0_size;
offset += tile.y * lod1_width;
offset += int(tile.y) * lod1_width;
break;
case 0:
default:
offset += tile.y * lod0_width;
offset += int(tile.y) * lod0_width;
break;
}
offset += tile.x;
offset += int(tile.x);
return offset;
}
@@ -92,13 +93,13 @@ int shadow_tile_offset(ivec2 tile, int tiles_index, int lod)
* \{ */
/** \note Will clamp if out of bounds. */
ShadowSamplingTile shadow_tile_load(usampler2D tilemaps_tx, ivec2 tile_co, int tilemap_index)
ShadowSamplingTile shadow_tile_load(usampler2D tilemaps_tx, uvec2 tile_co, int tilemap_index)
{
/* NOTE(@fclem): This clamp can hide some small imprecision at clip-map transition.
* Can be disabled to check if the clip-map is well centered. */
tile_co = clamp(tile_co, ivec2(0), ivec2(SHADOW_TILEMAP_RES - 1));
ivec2 texel = shadow_tile_coord_in_atlas(tile_co, tilemap_index);
uint tile_data = texelFetch(tilemaps_tx, texel, 0).x;
tile_co = clamp(tile_co, uvec2(0), uvec2(SHADOW_TILEMAP_RES - 1));
uvec2 texel = shadow_tile_coord_in_atlas(tile_co, tilemap_index);
uint tile_data = texelFetch(tilemaps_tx, ivec2(texel), 0).x;
return shadow_sampling_tile_unpack(tile_data);
}
@@ -202,14 +203,22 @@ int shadow_punctual_level(LightData light,
struct ShadowCoordinates {
/* Index of the tile-map to containing the tile. */
int tilemap_index;
/* LOD of the tile to load relative to the min level. Always positive. */
int lod_relative;
/* Tile coordinate inside the tile-map. */
ivec2 tile_coord;
/* UV coordinates in [0..SHADOW_TILEMAP_RES) range. */
vec2 uv;
/* Texel coordinates in [0..SHADOW_MAP_MAX_RES) range. */
uvec2 tilemap_texel;
/* Tile coordinate in [0..SHADOW_TILEMAP_RES) range. */
uvec2 tilemap_tile;
};
/* Assumes tilemap_uv is already saturated. */
ShadowCoordinates shadow_coordinate_from_uvs(int tilemap_index, vec2 tilemap_uv)
{
ShadowCoordinates ret;
ret.tilemap_index = tilemap_index;
ret.tilemap_texel = uvec2(tilemap_uv * (float(SHADOW_MAP_MAX_RES) - 1e-2));
ret.tilemap_tile = ret.tilemap_texel >> uint(SHADOW_PAGE_LOD);
return ret;
}
/* Retain sign bit and avoid costly int division. */
ivec2 shadow_decompress_grid_offset(eLightType light_type,
ivec2 offset_neg,
@@ -229,30 +238,24 @@ ivec2 shadow_decompress_grid_offset(eLightType light_type,
*/
ShadowCoordinates shadow_directional_coordinates_at_level(LightData light, vec3 lP, int level)
{
ShadowCoordinates ret;
/* This difference needs to be less than 32 for the later shift to be valid.
* This is ensured by `ShadowDirectional::clipmap_level_range()`. */
int level_relative = level - light_sun_data_get(light).clipmap_lod_min;
ret.tilemap_index = light.tilemap_index + level_relative;
ret.lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light_sun_data_get(light).clipmap_lod_min :
int lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light_sun_data_get(light).clipmap_lod_min :
level;
/* Compute offset in tile. */
ivec2 clipmap_offset = shadow_decompress_grid_offset(
light.type,
light_sun_data_get(light).clipmap_base_offset_neg,
light_sun_data_get(light).clipmap_base_offset_pos,
level_relative);
/* UV in [0..1] range over the tilemap. */
vec2 tilemap_uv = lP.xy - light_sun_data_get(light).clipmap_origin;
tilemap_uv *= exp2(float(-lod_relative));
tilemap_uv -= vec2(clipmap_offset) * (1.0 / float(SHADOW_TILEMAP_RES));
tilemap_uv = saturate(tilemap_uv + 0.5);
ret.uv = lP.xy - light_sun_data_get(light).clipmap_origin;
ret.uv /= exp2(float(ret.lod_relative));
ret.uv = ret.uv * float(SHADOW_TILEMAP_RES) + float(SHADOW_TILEMAP_RES / 2);
ret.uv -= vec2(clipmap_offset);
/* Clamp to avoid out of tile-map access. */
ret.tile_coord = clamp(ivec2(ret.uv), ivec2(0.0), ivec2(SHADOW_TILEMAP_RES - 1));
return ret;
return shadow_coordinate_from_uvs(light.tilemap_index + level_relative, tilemap_uv);
}
/**
@@ -325,16 +328,12 @@ ShadowCoordinates shadow_punctual_coordinates(LightData light, vec3 lP, int face
{
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light_local_data_get(light).clip_side;
ShadowCoordinates ret;
ret.tilemap_index = light.tilemap_index + face_id;
/* UVs in [-1..+1] range. */
ret.uv = (lP.xy * clip_near) / abs(lP.z * clip_side);
/* UVs in [0..SHADOW_TILEMAP_RES] range. */
ret.uv = ret.uv * float(SHADOW_TILEMAP_RES / 2) + float(SHADOW_TILEMAP_RES / 2);
/* Clamp to avoid out of tile-map access. */
ret.tile_coord = clamp(ivec2(ret.uv), ivec2(0), ivec2(SHADOW_TILEMAP_RES - 1));
return ret;
vec2 tilemap_uv = (lP.xy * clip_near) / abs(lP.z * clip_side);
/* UVs in [0..1] range. */
tilemap_uv = saturate(tilemap_uv * 0.5 + 0.5);
return shadow_coordinate_from_uvs(light.tilemap_index + face_id, tilemap_uv);
}
/** \} */

View File

@@ -15,47 +15,17 @@
#pragma BLENDER_REQUIRE(draw_view_lib.glsl)
#pragma BLENDER_REQUIRE(draw_math_geom_lib.glsl)
float shadow_read_depth_at_tilemap_uv(int tilemap_index, vec2 tilemap_uv)
{
/* Prevent out of bound access. Assumes the input is already non negative. */
tilemap_uv = min(tilemap_uv, vec2(0.99999));
ivec2 texel_coord = ivec2(tilemap_uv * float(SHADOW_MAP_MAX_RES));
/* Using bitwise ops is way faster than integer ops. */
const int page_shift = SHADOW_PAGE_LOD;
const int page_mask = ~(0xFFFFFFFF << SHADOW_PAGE_LOD);
ivec2 tile_coord = texel_coord >> page_shift;
ShadowSamplingTile tile = shadow_tile_load(shadow_tilemaps_tx, tile_coord, tilemap_index);
if (!tile.is_valid) {
return -1.0;
}
/* Shift LOD0 pixels so that they get wrapped at the right position for the given LOD. */
/* TODO convert everything to uint to avoid signed int operations. */
texel_coord += ivec2(tile.lod_offset << SHADOW_PAGE_LOD);
/* Scale to LOD pixels (merge LOD0 pixels together) then mask to get pixel in page. */
ivec2 texel_page = (texel_coord >> int(tile.lod)) & page_mask;
ivec3 texel = ivec3((ivec2(tile.page.xy) << page_shift) | texel_page, tile.page.z);
return uintBitsToFloat(texelFetch(shadow_atlas_tx, texel, 0).r);
}
/* ---------------------------------------------------------------------- */
/** \name Shadow Map Tracing loop
* \{ */
#define SHADOW_TRACING_INVALID_HISTORY -999.0
#define SHADOW_TRACING_INVALID_HISTORY FLT_MAX
struct ShadowMapTracingState {
/* Receiver Z value at previous valid depth sample. */
float receiver_depth_history;
/* Occluder Z value at previous valid depth sample. */
float occluder_depth_history;
/* Ray time at previous valid depth sample. */
float ray_time_history;
/* Z slope (delta/time) between previous valid sample (N-1) and the one before that (N-2). */
float occluder_depth_slope;
/* Occluder ray coordinate at previous valid depth sample. */
vec2 occluder_history;
/* Time slope between previous valid sample (N-1) and the one before that (N-2). */
float occluder_slope;
/* Multiplier and bias to the ray step quickly compute ray time. */
float ray_step_mul;
float ray_step_bias;
@@ -67,10 +37,8 @@ struct ShadowMapTracingState {
ShadowMapTracingState shadow_map_trace_init(int sample_count, float step_offset)
{
ShadowMapTracingState state;
state.receiver_depth_history = -1.0;
state.occluder_depth_history = SHADOW_TRACING_INVALID_HISTORY;
state.ray_time_history = -1.0;
state.occluder_depth_slope = 0.0;
state.occluder_history = vec2(SHADOW_TRACING_INVALID_HISTORY);
state.occluder_slope = SHADOW_TRACING_INVALID_HISTORY;
/* We trace the ray in reverse. From 1.0 (light) to 0.0 (shading point). */
state.ray_step_mul = -1.0 / float(sample_count);
state.ray_step_bias = 1.0 + step_offset * state.ray_step_mul;
@@ -79,8 +47,12 @@ ShadowMapTracingState shadow_map_trace_init(int sample_count, float step_offset)
}
struct ShadowTracingSample {
float receiver_depth;
float occluder_depth;
/**
* Occluder position in ray space.
* `x` component is just the normalized distance from the ray start to the ray end.
* `y` component is signed distance to the ray, positive if on the light side of the ray.
*/
vec2 occluder;
bool skip_sample;
};
@@ -91,7 +63,7 @@ struct ShadowTracingSample {
* Most of the code is wrapped into functions to avoid to debug issues inside macro code.
*/
#define SHADOW_MAP_TRACE_FN(ShadowRayType) \
ShadowMapTraceResult shadow_map_trace(ShadowRayType ray, int sample_count, float step_offset) \
bool shadow_map_trace(ShadowRayType ray, int sample_count, float step_offset) \
{ \
ShadowMapTracingState state = shadow_map_trace_init(sample_count, step_offset); \
for (int i = 0; (i <= sample_count) && (i <= SHADOW_MAX_STEP) && (state.hit == false); i++) { \
@@ -102,7 +74,7 @@ struct ShadowTracingSample {
\
shadow_map_trace_hit_check(state, samp); \
} \
return shadow_map_trace_finish(state); \
return state.hit; \
}
/**
@@ -119,68 +91,38 @@ void shadow_map_trace_hit_check(inout ShadowMapTracingState state, ShadowTracing
return;
}
/* For the first sample, regular depth compare since we do not have history values. */
if (state.occluder_depth_history == SHADOW_TRACING_INVALID_HISTORY) {
if (samp.occluder_depth < samp.receiver_depth) {
if (state.occluder_history.x == SHADOW_TRACING_INVALID_HISTORY) {
if (samp.occluder.x > state.ray_time) {
state.hit = true;
return;
}
state.occluder_depth_history = samp.occluder_depth;
state.receiver_depth_history = samp.receiver_depth;
state.ray_time_history = state.ray_time;
state.occluder_history = samp.occluder;
return;
}
/* Delta between previous valid sample. */
float ray_depth_delta = samp.receiver_depth - state.receiver_depth_history;
/* Delta between previous valid sample not occluding the ray. */
float time_delta = state.ray_time - state.ray_time_history;
/* Arbitrary increase the threshold to avoid missing occluders because of precision issues.
* Increasing the threshold inflates the occluders. */
float compare_threshold = abs(ray_depth_delta) * 1.05;
/* Find out if the ray step is behind an occluder.
* To be consider behind (and ignore the occluder), the occluder must not be cross the ray.
* Use the full delta ray depth as threshold to make sure to not miss any occluder. */
bool is_behind = samp.occluder_depth < (samp.receiver_depth - compare_threshold);
if (is_behind) {
/* Use last known valid occluder Z value and extrapolate to the sample position. */
samp.occluder_depth = state.occluder_depth_history + state.occluder_depth_slope * time_delta;
/* Intersection test will be against the extrapolated last known occluder. */
bool is_behind_occluder = samp.occluder.y > 0.0;
if (is_behind_occluder && (state.occluder_slope != SHADOW_TRACING_INVALID_HISTORY)) {
/* Extrapolate last known valid occluder and check if it crossed the ray.
* Note that we only want to check if the extrapolated occluder is above the ray at a certain
* time value, we don't actually care about the correct value. So we replace the complex
* problem of trying to get the extrapolation in shadow map space into the extrapolation at
* ray_time in ray space. This is equivalent as both functions have the same roots. */
float delta_time = state.ray_time - state.occluder_history.x;
float extrapolated_occluder_y = abs(state.occluder_history.y) +
state.occluder_slope * delta_time;
state.hit = extrapolated_occluder_y < 0.0;
}
else {
/* Compute current occluder slope and record history for when the ray goes behind a surface. */
state.occluder_depth_slope = (samp.occluder_depth - state.occluder_depth_history) / time_delta;
state.occluder_depth_slope = clamp(state.occluder_depth_slope, -100.0, 100.0);
state.occluder_depth_history = samp.occluder_depth;
state.ray_time_history = state.ray_time;
/* Intersection test will be against the current sample's occluder. */
vec2 delta = samp.occluder - state.occluder_history;
/* Clamping the slope to a mininim avoid light leaking. */
/* TODO(fclem): Expose as parameter? */
const float min_slope = tan(M_PI * 0.25);
state.occluder_slope = max(min_slope, abs(delta.y / delta.x));
state.occluder_history = samp.occluder;
/* Intersection test. Intersect if above the ray time. */
state.hit = samp.occluder.x > state.ray_time;
}
if (samp.occluder_depth < samp.receiver_depth) {
state.occluder_depth_history = samp.occluder_depth;
state.hit = true;
return;
}
/* No intersection. */
state.receiver_depth_history = samp.receiver_depth;
}
struct ShadowMapTraceResult {
bool has_hit;
float occluder_depth;
};
ShadowMapTraceResult shadow_map_trace_finish(ShadowMapTracingState state)
{
ShadowMapTraceResult result;
if (state.hit) {
result.occluder_depth = state.occluder_depth_history;
result.has_hit = true;
}
else {
result.occluder_depth = 0.0;
result.has_hit = false;
}
return result;
}
/** \} */
@@ -202,12 +144,15 @@ vec3 shadow_ray_above_horizon_ensure(vec3 L, vec3 N)
* \{ */
struct ShadowRayDirectional {
/* Ray in local translated coordinate, with depth in [0..1] range in W component. */
vec4 origin;
vec4 direction;
/* Ray in light rotated space. But not translated. */
vec3 origin;
vec3 direction;
/* Convert form local light position to ray oriented position where X axis is the ray. */
vec3 local_ray_up;
LightData light;
};
/* `lP` is supposed to be in light rotated space. But not translated. */
ShadowRayDirectional shadow_ray_generate_directional(LightData light,
vec2 random_2d,
vec3 lP,
@@ -216,28 +161,25 @@ ShadowRayDirectional shadow_ray_generate_directional(LightData light,
float clip_near = orderedIntBitsToFloat(light.clip_near);
float clip_far = orderedIntBitsToFloat(light.clip_far);
/* Assumed to be non-null. */
float z_range = clip_far - clip_near;
float dist_to_near_plane = -lP.z - clip_near;
/* `lP` is supposed to be in light rotated space. But not translated. */
vec4 origin = vec4(lP, dist_to_near_plane / z_range);
vec3 disk_direction = sample_uniform_cone(sample_cylinder(random_2d),
light_sun_data_get(light).shadow_angle);
disk_direction = shadow_ray_above_horizon_ensure(disk_direction, lNg);
/* Light shape is 1 unit away from the shading point. */
vec4 direction = vec4(disk_direction, -1.0 / z_range);
vec3 direction = sample_uniform_cone(sample_cylinder(random_2d),
light_sun_data_get(light).shadow_angle);
direction = shadow_ray_above_horizon_ensure(direction, lNg);
/* 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,
dist_to_near_plane / disk_direction.z);
dist_to_near_plane / direction.z);
ShadowRayDirectional ray;
ray.origin = origin;
ray.origin = lP;
ray.direction = direction;
ray.light = light;
/* TODO(fclem): We can simplify this using the ray direction construction. */
ray.local_ray_up = safe_normalize(
cross(cross(vec3(0.0, 0.0, -1.0), ray.direction), ray.direction));
return ray;
}
@@ -245,37 +187,21 @@ ShadowTracingSample shadow_map_trace_sample(ShadowMapTracingState state,
inout ShadowRayDirectional ray)
{
/* Ray position is ray local position with origin at light origin. */
vec4 ray_pos = ray.origin + ray.direction * state.ray_time;
vec3 ray_pos = ray.origin + ray.direction * state.ray_time;
int level = shadow_directional_level(ray.light, ray_pos.xyz - light_position_get(ray.light));
/* This difference needs to be less than 32 for the later shift to be valid.
* This is ensured by ShadowDirectional::clipmap_level_range(). */
int level_relative = level - light_sun_data_get(ray.light).clipmap_lod_min;
ShadowCoordinates coord = shadow_directional_coordinates(ray.light, ray_pos);
int lod_relative = (ray.light.type == LIGHT_SUN_ORTHO) ?
light_sun_data_get(ray.light).clipmap_lod_min :
level;
vec2 clipmap_origin = light_sun_data_get(ray.light).clipmap_origin;
vec2 clipmap_pos = ray_pos.xy - clipmap_origin;
vec2 tilemap_uv = clipmap_pos * exp2(-float(lod_relative)) + 0.5;
/* Compute offset in tile. */
ivec2 clipmap_offset = shadow_decompress_grid_offset(
ray.light.type,
light_sun_data_get(ray.light).clipmap_base_offset_neg,
light_sun_data_get(ray.light).clipmap_base_offset_pos,
level_relative);
/* Translate tilemap UVs to its origin. */
tilemap_uv -= vec2(clipmap_offset) / float(SHADOW_TILEMAP_RES);
/* Clamp to avoid out of tilemap access. */
tilemap_uv = saturate(tilemap_uv);
float depth = shadow_read_depth(shadow_atlas_tx, shadow_tilemaps_tx, coord);
/* Distance from near plane. */
float clip_near = orderedIntBitsToFloat(ray.light.clip_near);
vec3 occluder_pos = vec3(ray_pos.xy, -depth - clip_near);
/* Transform to ray local space. */
vec3 ray_local_occluder = occluder_pos - ray.origin;
ShadowTracingSample samp;
samp.receiver_depth = ray_pos.w;
samp.occluder_depth = shadow_read_depth_at_tilemap_uv(ray.light.tilemap_index + level_relative,
tilemap_uv);
samp.skip_sample = (samp.occluder_depth == -1.0);
samp.occluder.x = dot(ray_local_occluder, ray.direction) / length_squared(ray.direction);
samp.occluder.y = dot(ray_local_occluder, ray.local_ray_up);
samp.skip_sample = (depth == -1.0);
return samp;
}
@@ -288,11 +214,14 @@ SHADOW_MAP_TRACE_FN(ShadowRayDirectional)
* \{ */
struct ShadowRayPunctual {
/* Ray in tile-map normalized coordinates [0..1]. */
/* Light space shadow ray origin and direction. */
vec3 origin;
vec3 direction;
/* Convert form local light position to ray oriented position where X axis is the ray. */
vec3 local_ray_up;
/* Tile-map to sample. */
int tilemap_index;
int light_tilemap_index;
LightData light;
};
/* Return ray in UV clip space [0..1]. */
@@ -354,44 +283,34 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light, vec2 random_2d,
direction *= saturate((dist - clip_distance) / dist);
}
/* Apply shadow origin shift. */
vec3 local_ray_start = lP + projection_origin;
vec3 local_ray_end = local_ray_start + direction;
/* Use an offset in the ray direction to jitter which face is traced.
* This helps hiding some harsh discontinuity. */
int face_id = shadow_punctual_face_index_get(local_ray_start + direction * 0.5);
/* Local Light Space > Face Local (View) Space. */
vec3 view_ray_start = shadow_punctual_local_position_to_face_local(face_id, local_ray_start);
vec3 view_ray_end = shadow_punctual_local_position_to_face_local(face_id, local_ray_end);
/* Face Local (View) Space > Clip Space. */
/* TODO: Could be simplified since frustum is completely symmetrical. */
mat4 winmat = projection_perspective(
-clip_side, clip_side, -clip_side, clip_side, clip_near, clip_far);
vec3 clip_ray_start = project_point(winmat, view_ray_start);
vec3 clip_ray_end = project_point(winmat, view_ray_end);
/* Clip Space > UV Space. */
vec3 uv_ray_start = clip_ray_start * 0.5 + 0.5;
vec3 uv_ray_end = clip_ray_end * 0.5 + 0.5;
/* Compute the ray again. */
ShadowRayPunctual ray;
ray.origin = uv_ray_start;
ray.direction = uv_ray_end - uv_ray_start;
ray.tilemap_index = light.tilemap_index + face_id;
ray.origin = lP;
ray.direction = direction;
ray.light_tilemap_index = light.tilemap_index;
ray.local_ray_up = safe_normalize(cross(cross(ray.origin, ray.direction), ray.direction));
ray.light = light;
return ray;
}
ShadowTracingSample shadow_map_trace_sample(ShadowMapTracingState state,
inout ShadowRayPunctual ray)
{
vec3 ray_pos = ray.origin + ray.direction * state.ray_time;
vec2 tilemap_uv = saturate(ray_pos.xy);
vec3 receiver_pos = ray.origin + ray.direction * state.ray_time;
int face_id = shadow_punctual_face_index_get(receiver_pos);
vec3 face_pos = shadow_punctual_local_position_to_face_local(face_id, receiver_pos);
ShadowCoordinates coord = shadow_punctual_coordinates(ray.light, face_pos, face_id);
float radial_occluder_depth = shadow_read_depth(shadow_atlas_tx, shadow_tilemaps_tx, coord);
vec3 occluder_pos = receiver_pos * (radial_occluder_depth / length(receiver_pos));
/* Transform to ray local space. */
vec3 ray_local_occluder = occluder_pos - ray.origin;
ShadowTracingSample samp;
samp.receiver_depth = ray_pos.z;
samp.occluder_depth = shadow_read_depth_at_tilemap_uv(ray.tilemap_index, tilemap_uv);
samp.skip_sample = (samp.occluder_depth == -1.0);
samp.occluder.x = dot(ray_local_occluder, ray.direction) / length_squared(ray.direction);
samp.occluder.y = dot(ray_local_occluder, ray.local_ray_up);
samp.skip_sample = (radial_occluder_depth == -1.0);
return samp;
}
@@ -487,17 +406,18 @@ float shadow_normal_offset(vec3 Ng, vec3 L)
/**
* Evaluate shadowing by casting rays toward the light direction.
* Returns light visibility.
*/
ShadowEvalResult shadow_eval(LightData light,
const bool is_directional,
const bool is_transmission,
bool is_translucent_with_thickness,
float thickness, /* Only used if is_transmission is true. */
vec3 P,
vec3 Ng,
vec3 L,
int ray_count,
int ray_step_count)
float shadow_eval(LightData light,
const bool is_directional,
const bool is_transmission,
bool is_translucent_with_thickness,
float thickness, /* Only used if is_transmission is true. */
vec3 P,
vec3 Ng,
vec3 L,
int ray_count,
int ray_step_count)
{
#if defined(EEVEE_SAMPLING_DATA) && defined(EEVEE_UTILITY_TX)
# ifdef GPU_FRAGMENT_SHADER
@@ -506,7 +426,7 @@ ShadowEvalResult shadow_eval(LightData light,
vec2 pixel = vec2(gl_GlobalInvocationID.xy);
# endif
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);
vec3 random_shadow_3d = fract(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));
#else
/* Case of surfel light eval. */
@@ -546,24 +466,21 @@ ShadowEvalResult shadow_eval(LightData light,
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);
ShadowMapTraceResult trace;
bool has_hit;
if (is_directional) {
ShadowRayDirectional clip_ray = shadow_ray_generate_directional(
light, random_ray_2d, lP, lNg);
trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z);
has_hit = 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);
trace = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z);
has_hit = shadow_map_trace(clip_ray, ray_step_count, random_shadow_3d.z);
}
surface_hit += float(trace.has_hit);
surface_hit += float(has_hit);
}
/* Average samples. */
ShadowEvalResult result;
result.light_visibilty = saturate(1.0 - surface_hit / float(ray_count));
result.occluder_distance = 0.0; /* Unused. Could reintroduced if needed. */
return result;
return saturate(1.0 - surface_hit / float(ray_count));
}
/** \} */

View File

@@ -162,6 +162,14 @@ void shadow_viewport_layer_set(int view_id, int lod)
gpu_ViewportIndex = lod;
}
vec3 shadow_position_vector_get(vec3 view_position, ShadowRenderView view)
{
if (view.is_directionnal) {
return vec3(0.0, 0.0, -view_position.z - view.clip_near);
}
return view_position;
}
/* 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. */

View File

@@ -24,21 +24,19 @@ vec4 closure_to_rgba(Closure cl)
void main()
{
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. */
float ndc_depth = gl_FragCoord.z;
float linear_depth = length(shadow_clip.position);
#ifdef SHADOW_UPDATE_TBDR
/* We need to write to `gl_FragDepth` un-conditionally. So we cannot early exit or use discard. */
# define discard_result f_depth = 1.0;
# define discard_result \
linear_depth = FLT_MAX; \
ndc_depth = 1.0;
#else
# define discard_result \
discard; \
return;
#endif
/* Avoid values greater than 1. */
f_depth = saturate(f_depth);
/* Clip to light shape. */
if (length_squared(shadow_clip.vector) < 1.0) {
@@ -83,12 +81,12 @@ void main()
ivec3 out_texel = ivec3((page.xy << page_shift) | texel_page, page.z);
uint u_depth = floatBitsToUint(f_depth);
uint u_depth = floatBitsToUint(linear_depth);
imageAtomicMin(shadow_atlas_img, out_texel, u_depth);
#endif
#ifdef SHADOW_UPDATE_TBDR
gl_FragDepth = f_depth;
out_depth = f_depth;
gl_FragDepth = ndc_depth;
out_depth = linear_depth;
#endif
}

View File

@@ -39,8 +39,10 @@ vec3 volume_light_eval(const bool is_directional, vec3 P, vec3 V, uint l_idx, fl
float visibility = attenuation;
if (light.tilemap_index != LIGHT_NO_SHADOW) {
visibility *= shadow_sample(is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P)
.light_visibilty;
float delta = shadow_sample(is_directional, shadow_atlas_tx, shadow_tilemaps_tx, light, P);
if (delta > 0.0) {
return vec3(0);
}
}
visibility *= volume_phase_function(-V, lv.L, s_anisotropy);
if (visibility < LIGHT_ATTENUATION_THRESHOLD) {

View File

@@ -232,17 +232,14 @@ 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, "position")
.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,13 +234,12 @@ 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, 0.0f);
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemaps_data.append(tilemap);
}
{
ShadowTileMap tilemap(1 * SHADOW_TILEDATA_PER_TILEMAP);
tilemap.sync_orthographic(
float4x4::identity(), int2(0), 1, 0.0f, 0.0f, SHADOW_PROJECTION_CLIPMAP);
tilemap.sync_orthographic(float4x4::identity(), int2(0), 1, 0.0f, SHADOW_PROJECTION_CLIPMAP);
tilemaps_data.append(tilemap);
}
@@ -1544,7 +1543,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, 0.0f);
LIGHT_OMNI_SPHERE, float4x4::identity(), 0.01f, 1.0f, 0.01f, 0.0f, Z_NEG, 0.0f);
tilemaps_data.append(tilemap);
}