summaryrefslogtreecommitdiff
path: root/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
diff options
context:
space:
mode:
authorjfons <joan.fonssanchez@gmail.com>2021-08-04 17:18:06 +0200
committerjfons <joan.fonssanchez@gmail.com>2021-08-19 13:46:51 +0200
commit55e7832d7b2ff4f6c9458962977d75970e581741 (patch)
tree534429b5f0c7fe788023e7308b09defeac22098f /servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
parent8b6c168b3ac14d9ac6350d03e7be4d196311e0fe (diff)
Improvements to SpotLight3D and OmniLight3D's shadows
OmniLight3D: * Fixed lack of precision in cube map mode by scaling the projection's znear. * Fixed aliasing issues by making the paraboloids use two square regions instead of two half squares. * Fixed shadowmap atlas bleeding by adding padding. * Fixed sihadow blur's inconsistent radius and unclamped sampling. SpotLight3D: * Fixed lack of precision by scaling the projection's znear. * Fixed normal biasing. Both: * Tweaked biasing to make sure it works out of the box in most situations.
Diffstat (limited to 'servers/rendering/renderer_rd/renderer_scene_render_rd.cpp')
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp248
1 files changed, 173 insertions, 75 deletions
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index d81c216f37..8496ef631b 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -860,7 +860,7 @@ void RendererSceneRenderRD::shadow_atlas_set_size(RID p_atlas, int p_size, bool
shadow_atlas->shadow_owners.clear();
shadow_atlas->size = p_size;
- shadow_atlas->use_16_bits = p_size;
+ shadow_atlas->use_16_bits = p_16_bits;
}
void RendererSceneRenderRD::shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) {
@@ -935,7 +935,7 @@ bool RendererSceneRenderRD::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas,
//look for an empty space
int sc = shadow_atlas->quadrants[qidx].shadows.size();
- ShadowAtlas::Quadrant::Shadow *sarr = shadow_atlas->quadrants[qidx].shadows.ptrw();
+ const ShadowAtlas::Quadrant::Shadow *sarr = shadow_atlas->quadrants[qidx].shadows.ptr();
int found_free_idx = -1; //found a free one
int found_used_idx = -1; //found existing one, must steal it
@@ -980,6 +980,78 @@ bool RendererSceneRenderRD::_shadow_atlas_find_shadow(ShadowAtlas *shadow_atlas,
return false;
}
+bool RendererSceneRenderRD::_shadow_atlas_find_omni_shadows(ShadowAtlas *shadow_atlas, int *p_in_quadrants, int p_quadrant_count, int p_current_subdiv, uint64_t p_tick, int &r_quadrant, int &r_shadow) {
+ for (int i = p_quadrant_count - 1; i >= 0; i--) {
+ int qidx = p_in_quadrants[i];
+
+ if (shadow_atlas->quadrants[qidx].subdivision == (uint32_t)p_current_subdiv) {
+ return false;
+ }
+
+ //look for an empty space
+ int sc = shadow_atlas->quadrants[qidx].shadows.size();
+ const ShadowAtlas::Quadrant::Shadow *sarr = shadow_atlas->quadrants[qidx].shadows.ptr();
+
+ int found_idx = -1;
+ uint64_t min_pass = 0; // sum of currently selected spots, try to get the least recently used pair
+
+ for (int j = 0; j < sc - 1; j++) {
+ uint64_t pass = 0;
+
+ if (sarr[j].owner.is_valid()) {
+ LightInstance *sli = light_instance_owner.getornull(sarr[j].owner);
+ ERR_CONTINUE(!sli);
+
+ if (sli->last_scene_pass == scene_pass) {
+ continue;
+ }
+
+ //was just allocated, don't kill it so soon, wait a bit..
+ if (p_tick - sarr[j].alloc_tick < shadow_atlas_realloc_tolerance_msec) {
+ continue;
+ }
+ pass += sli->last_scene_pass;
+ }
+
+ if (sarr[j + 1].owner.is_valid()) {
+ LightInstance *sli = light_instance_owner.getornull(sarr[j + 1].owner);
+ ERR_CONTINUE(!sli);
+
+ if (sli->last_scene_pass == scene_pass) {
+ continue;
+ }
+
+ //was just allocated, don't kill it so soon, wait a bit..
+ if (p_tick - sarr[j + 1].alloc_tick < shadow_atlas_realloc_tolerance_msec) {
+ continue;
+ }
+ pass += sli->last_scene_pass;
+ }
+
+ if (found_idx == -1 || pass < min_pass) {
+ found_idx = j;
+ min_pass = pass;
+
+ // we found two empty spots, no need to check the rest
+ if (pass == 0) {
+ break;
+ }
+ }
+ }
+
+ if (found_idx == -1) {
+ continue; //nothing found
+ }
+
+ r_quadrant = qidx;
+ r_shadow = found_idx;
+
+ return true;
+ }
+
+ return false;
+}
+
bool RendererSceneRenderRD::shadow_atlas_update_light(RID p_atlas, RID p_light_intance, float p_coverage, uint64_t p_light_version) {
ShadowAtlas *shadow_atlas = shadow_atlas_owner.getornull(p_atlas);
ERR_FAIL_COND_V(!shadow_atlas, false);
@@ -1025,94 +1097,104 @@ bool RendererSceneRenderRD::shadow_atlas_update_light(RID p_atlas, RID p_light_i
uint64_t tick = OS::get_singleton()->get_ticks_msec();
- //see if it already exists
+ uint32_t old_key = ShadowAtlas::SHADOW_INVALID;
+ uint32_t old_quadrant = ShadowAtlas::SHADOW_INVALID;
+ uint32_t old_shadow = ShadowAtlas::SHADOW_INVALID;
+ int old_subdivision = -1;
+
+ bool should_realloc = false;
+ bool should_redraw = false;
if (shadow_atlas->shadow_owners.has(p_light_intance)) {
- //it does!
- uint32_t key = shadow_atlas->shadow_owners[p_light_intance];
- uint32_t q = (key >> ShadowAtlas::QUADRANT_SHIFT) & 0x3;
- uint32_t s = key & ShadowAtlas::SHADOW_INDEX_MASK;
+ old_key = shadow_atlas->shadow_owners[p_light_intance];
+ old_quadrant = (old_key >> ShadowAtlas::QUADRANT_SHIFT) & 0x3;
+ old_shadow = old_key & ShadowAtlas::SHADOW_INDEX_MASK;
- bool should_realloc = shadow_atlas->quadrants[q].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[q].shadows[s].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec);
- bool should_redraw = shadow_atlas->quadrants[q].shadows[s].version != p_light_version;
+ should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec);
+ should_redraw = shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].version != p_light_version;
if (!should_realloc) {
- shadow_atlas->quadrants[q].shadows.write[s].version = p_light_version;
+ shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow].version = p_light_version;
//already existing, see if it should redraw or it's just OK
return should_redraw;
}
- int new_quadrant, new_shadow;
+ old_subdivision = shadow_atlas->quadrants[old_quadrant].subdivision;
+ }
- //find a better place
- if (_shadow_atlas_find_shadow(shadow_atlas, valid_quadrants, valid_quadrant_count, shadow_atlas->quadrants[q].subdivision, tick, new_quadrant, new_shadow)) {
- //found a better place!
- ShadowAtlas::Quadrant::Shadow *sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_shadow];
- if (sh->owner.is_valid()) {
- //is taken, but is invalid, erasing it
- shadow_atlas->shadow_owners.erase(sh->owner);
- LightInstance *sli = light_instance_owner.getornull(sh->owner);
- sli->shadow_atlases.erase(p_atlas);
- }
+ bool is_omni = li->light_type == RS::LIGHT_OMNI;
+ bool found_shadow = false;
+ int new_quadrant = -1;
+ int new_shadow = -1;
- //erase previous
- shadow_atlas->quadrants[q].shadows.write[s].version = 0;
- shadow_atlas->quadrants[q].shadows.write[s].owner = RID();
+ if (is_omni) {
+ found_shadow = _shadow_atlas_find_omni_shadows(shadow_atlas, valid_quadrants, valid_quadrant_count, old_subdivision, tick, new_quadrant, new_shadow);
+ } else {
+ found_shadow = _shadow_atlas_find_shadow(shadow_atlas, valid_quadrants, valid_quadrant_count, old_subdivision, tick, new_quadrant, new_shadow);
+ }
- sh->owner = p_light_intance;
- sh->alloc_tick = tick;
- sh->version = p_light_version;
- li->shadow_atlases.insert(p_atlas);
-
- //make new key
- key = new_quadrant << ShadowAtlas::QUADRANT_SHIFT;
- key |= new_shadow;
- //update it in map
- shadow_atlas->shadow_owners[p_light_intance] = key;
- //make it dirty, as it should redraw anyway
- return true;
+ if (found_shadow) {
+ if (old_quadrant != ShadowAtlas::SHADOW_INVALID) {
+ shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow].version = 0;
+ shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow].owner = RID();
+
+ if (old_key & ShadowAtlas::OMNI_LIGHT_FLAG) {
+ shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow + 1].version = 0;
+ shadow_atlas->quadrants[old_quadrant].shadows.write[old_shadow + 1].owner = RID();
+ }
}
- //no better place for this shadow found, keep current
+ uint32_t new_key = new_quadrant << ShadowAtlas::QUADRANT_SHIFT;
+ new_key |= new_shadow;
- //already existing, see if it should redraw or it's just OK
+ ShadowAtlas::Quadrant::Shadow *sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_shadow];
+ _shadow_atlas_invalidate_shadow(sh, p_atlas, shadow_atlas, new_quadrant, new_shadow);
- shadow_atlas->quadrants[q].shadows.write[s].version = p_light_version;
+ sh->owner = p_light_intance;
+ sh->alloc_tick = tick;
+ sh->version = p_light_version;
- return should_redraw;
- }
+ if (is_omni) {
+ new_key |= ShadowAtlas::OMNI_LIGHT_FLAG;
- int new_quadrant, new_shadow;
+ int new_omni_shadow = new_shadow + 1;
+ ShadowAtlas::Quadrant::Shadow *extra_sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_omni_shadow];
+ _shadow_atlas_invalidate_shadow(extra_sh, p_atlas, shadow_atlas, new_quadrant, new_omni_shadow);
- //find a better place
- if (_shadow_atlas_find_shadow(shadow_atlas, valid_quadrants, valid_quadrant_count, -1, tick, new_quadrant, new_shadow)) {
- //found a better place!
- ShadowAtlas::Quadrant::Shadow *sh = &shadow_atlas->quadrants[new_quadrant].shadows.write[new_shadow];
- if (sh->owner.is_valid()) {
- //is taken, but is invalid, erasing it
- shadow_atlas->shadow_owners.erase(sh->owner);
- LightInstance *sli = light_instance_owner.getornull(sh->owner);
- sli->shadow_atlases.erase(p_atlas);
+ extra_sh->owner = p_light_intance;
+ extra_sh->alloc_tick = tick;
+ extra_sh->version = p_light_version;
}
- sh->owner = p_light_intance;
- sh->alloc_tick = tick;
- sh->version = p_light_version;
li->shadow_atlases.insert(p_atlas);
- //make new key
- uint32_t key = new_quadrant << ShadowAtlas::QUADRANT_SHIFT;
- key |= new_shadow;
//update it in map
- shadow_atlas->shadow_owners[p_light_intance] = key;
+ shadow_atlas->shadow_owners[p_light_intance] = new_key;
//make it dirty, as it should redraw anyway
-
return true;
}
- //no place to allocate this light, apologies
+ return should_redraw;
+}
- return false;
+void RendererSceneRenderRD::_shadow_atlas_invalidate_shadow(RendererSceneRenderRD::ShadowAtlas::Quadrant::Shadow *p_shadow, RID p_atlas, RendererSceneRenderRD::ShadowAtlas *p_shadow_atlas, uint32_t p_quadrant, uint32_t p_shadow_idx) {
+ if (p_shadow->owner.is_valid()) {
+ LightInstance *sli = light_instance_owner.getornull(p_shadow->owner);
+ uint32_t old_key = p_shadow_atlas->shadow_owners[p_shadow->owner];
+
+ if (old_key & ShadowAtlas::OMNI_LIGHT_FLAG) {
+ uint32_t s = old_key & ShadowAtlas::SHADOW_INDEX_MASK;
+ uint32_t omni_shadow_idx = p_shadow_idx + (s == (uint32_t)p_shadow_idx ? 1 : -1);
+ RendererSceneRenderRD::ShadowAtlas::Quadrant::Shadow *omni_shadow = &p_shadow_atlas->quadrants[p_quadrant].shadows.write[omni_shadow_idx];
+ omni_shadow->version = 0;
+ omni_shadow->owner = RID();
+ }
+
+ p_shadow->version = 0;
+ p_shadow->owner = RID();
+ sli->shadow_atlases.erase(p_atlas);
+ p_shadow_atlas->shadow_owners.erase(p_shadow->owner);
+ }
}
void RendererSceneRenderRD::_update_directional_shadow_atlas() {
@@ -1137,6 +1219,7 @@ void RendererSceneRenderRD::directional_shadow_atlas_set_size(int p_size, bool p
}
directional_shadow.size = p_size;
+ directional_shadow.use_16_bits = p_16_bits;
if (directional_shadow.depth.is_valid()) {
RD::get_singleton()->free(directional_shadow.depth);
@@ -3120,18 +3203,19 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
light_data.shadow_enabled = true;
- if (type == RS::LIGHT_SPOT) {
- light_data.shadow_bias = (storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) / 100.0);
+ float shadow_texel_size = light_instance_get_shadow_texel_size(li->self, p_shadow_atlas);
+ light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size * 10.0;
- } else { //omni
+ if (type == RS::LIGHT_SPOT) {
light_data.shadow_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS) / 100.0;
- float shadow_texel_size = light_instance_get_shadow_texel_size(li->self, p_shadow_atlas);
- light_data.shadow_normal_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS) * shadow_texel_size * 2.0; // applied in -1 .. 1 space
+ } else { //omni
+ light_data.shadow_bias = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BIAS);
}
light_data.transmittance_bias = storage->light_get_transmittance_bias(base);
- Rect2 rect = light_instance_get_shadow_atlas_rect(li->self, p_shadow_atlas);
+ Vector2i omni_offset;
+ Rect2 rect = light_instance_get_shadow_atlas_rect(li->self, p_shadow_atlas, omni_offset);
light_data.atlas_rect[0] = rect.position.x;
light_data.atlas_rect[1] = rect.position.y;
@@ -3142,7 +3226,6 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
light_data.shadow_volumetric_fog_fade = 1.0 / storage->light_get_shadow_volumetric_fog_fade(base);
if (type == RS::LIGHT_OMNI) {
- light_data.atlas_rect[3] *= 0.5; //one paraboloid on top of another
Transform3D proj = (inverse_transform * light_transform).inverse();
RendererStorageRD::store_transform(proj, light_data.shadow_matrix);
@@ -3154,6 +3237,8 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
light_data.soft_shadow_scale *= shadows_quality_radius_get(); // Only use quality radius for PCF
}
+ light_data.direction[0] = omni_offset.x * float(rect.size.width);
+ light_data.direction[1] = omni_offset.y * float(rect.size.height);
} else if (type == RS::LIGHT_SPOT) {
Transform3D modelview = (inverse_transform * light_transform).inverse();
CameraMatrix bias;
@@ -4139,6 +4224,7 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas,
bool using_dual_paraboloid = false;
bool using_dual_paraboloid_flip = false;
+ Vector2i dual_paraboloid_offset;
RID render_fb;
RID render_texture;
float zfar;
@@ -4232,6 +4318,9 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas,
zfar = storage->light_get_param(light_instance->light, RS::LIGHT_PARAM_RANGE);
if (storage->light_get_type(light_instance->light) == RS::LIGHT_OMNI) {
+ bool wrap = (shadow + 1) % shadow_atlas->quadrants[quadrant].subdivision == 0;
+ dual_paraboloid_offset = wrap ? Vector2i(1 - shadow_atlas->quadrants[quadrant].subdivision, 1) : Vector2i(1, 0);
+
if (storage->light_omni_get_shadow_mode(light_instance->light) == RS::LIGHT_OMNI_SHADOW_CUBE) {
ShadowCubemap *cubemap = _get_shadow_cubemap(shadow_size / 2);
@@ -4251,12 +4340,16 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas,
}
} else {
+ atlas_rect.position.x += 1;
+ atlas_rect.position.y += 1;
+ atlas_rect.size.x -= 2;
+ atlas_rect.size.y -= 2;
+
+ atlas_rect.position += p_pass * atlas_rect.size * dual_paraboloid_offset;
+
light_projection = light_instance->shadow_transform[0].camera;
light_transform = light_instance->shadow_transform[0].transform;
- atlas_rect.size.height /= 2;
- atlas_rect.position.y += p_pass * atlas_rect.size.height;
-
using_dual_paraboloid = true;
using_dual_paraboloid_flip = p_pass == 1;
render_fb = shadow_atlas->fb;
@@ -4285,10 +4378,9 @@ void RendererSceneRenderRD::_render_shadow_pass(RID p_light, RID p_shadow_atlas,
atlas_rect_norm.position.y /= float(atlas_size);
atlas_rect_norm.size.x /= float(atlas_size);
atlas_rect_norm.size.y /= float(atlas_size);
- atlas_rect_norm.size.height /= 2;
- storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, light_projection.get_z_near(), light_projection.get_z_far(), false);
- atlas_rect_norm.position.y += atlas_rect_norm.size.height;
- storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, light_projection.get_z_near(), light_projection.get_z_far(), true);
+ storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), false);
+ atlas_rect_norm.position += Vector2(dual_paraboloid_offset) * atlas_rect_norm.size;
+ storage->get_effects()->copy_cubemap_to_dp(render_texture, atlas_fb, atlas_rect_norm, atlas_rect.size, light_projection.get_z_near(), light_projection.get_z_far(), true);
//restore transform so it can be properly used
light_instance_set_shadow_transform(p_light, CameraMatrix(), light_instance->transform, zfar, 0, 0, 0);
@@ -4390,6 +4482,12 @@ bool RendererSceneRenderRD::free(RID p_rid) {
uint32_t s = key & ShadowAtlas::SHADOW_INDEX_MASK;
shadow_atlas->quadrants[q].shadows.write[s].owner = RID();
+
+ if (key & ShadowAtlas::OMNI_LIGHT_FLAG) {
+ // Omni lights use two atlas spots, make sure to clear the other as well
+ shadow_atlas->quadrants[q].shadows.write[s + 1].owner = RID();
+ }
+
shadow_atlas->shadow_owners.erase(p_rid);
}