From 55e7832d7b2ff4f6c9458962977d75970e581741 Mon Sep 17 00:00:00 2001 From: jfons Date: Wed, 4 Aug 2021 17:18:06 +0200 Subject: [PATCH] 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. --- doc/classes/DirectionalLight3D.xml | 2 +- doc/classes/Light3D.xml | 4 +- scene/3d/light_3d.cpp | 9 +- servers/rendering/renderer_rd/effects_rd.cpp | 6 +- servers/rendering/renderer_rd/effects_rd.h | 5 +- .../renderer_rd/renderer_scene_render_rd.cpp | 256 ++++++++++++------ .../renderer_rd/renderer_scene_render_rd.h | 23 +- .../renderer_rd/shaders/cube_to_dp.glsl | 14 +- .../shaders/scene_forward_clustered.glsl | 4 +- .../shaders/scene_forward_lights_inc.glsl | 142 ++++++---- .../shaders/scene_forward_mobile.glsl | 4 +- servers/rendering/renderer_scene_cull.cpp | 11 +- 12 files changed, 308 insertions(+), 172 deletions(-) diff --git a/doc/classes/DirectionalLight3D.xml b/doc/classes/DirectionalLight3D.xml index 0060368207..e3badea0f4 100644 --- a/doc/classes/DirectionalLight3D.xml +++ b/doc/classes/DirectionalLight3D.xml @@ -36,7 +36,7 @@ The distance from shadow split 2 to split 3. Relative to [member directional_shadow_max_distance]. Only used when [member directional_shadow_mode] is [code]SHADOW_PARALLEL_4_SPLITS[/code]. - + If [code]true[/code], this [DirectionalLight3D] will not be used for anything except sky shaders. Use this for lights that impact your sky shader that you may want to hide from affecting the rest of the scene. For example, you may want to enable this when the sun in your sky shader falls below the horizon. diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index 380e9314d4..cd2f4eca18 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -61,7 +61,7 @@ The intensity of the specular blob in objects affected by the light. At [code]0[/code], the light becomes a pure diffuse light. When not baking emission, this can be used to avoid unrealistic reflections when placing lights above an emissive surface. - + Used to adjust shadow appearance. Too small a value results in self-shadowing ("shadow acne"), while too large a value causes shadows to separate from casters ("peter-panning"). Adjust as needed. @@ -75,7 +75,7 @@ - + Offsets the lookup into the shadow map by the object's normal. This can be used to reduce self-shadowing artifacts without using [member shadow_bias]. In practice, this value should be tweaked along with [member shadow_bias] to reduce artifacts as much as possible. diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 508f8a70df..ab417fafdd 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -212,10 +212,6 @@ void Light3D::_validate_property(PropertyInfo &property) const { property.usage = PROPERTY_USAGE_NONE; } - if (get_light_type() == RS::LIGHT_SPOT && property.name == "shadow_normal_bias") { - property.usage = PROPERTY_USAGE_NONE; - } - if (get_light_type() == RS::LIGHT_DIRECTIONAL && property.name == "light_projector") { property.usage = PROPERTY_USAGE_NONE; } @@ -425,9 +421,7 @@ DirectionalLight3D::DirectionalLight3D() : set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_FADE_START, 0.8); // Increase the default shadow bias to better suit most scenes. - // Leave normal bias untouched as it doesn't benefit DirectionalLight3D as much as OmniLight3D. set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0); set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); blend_splits = false; } @@ -468,8 +462,7 @@ OmniLight3D::OmniLight3D() : Light3D(RenderingServer::LIGHT_OMNI) { set_shadow_mode(SHADOW_CUBE); // Increase the default shadow biases to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.1); - set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0); + set_param(PARAM_SHADOW_BIAS, 0.2); } TypedArray SpotLight3D::get_configuration_warnings() const { diff --git a/servers/rendering/renderer_rd/effects_rd.cpp b/servers/rendering/renderer_rd/effects_rd.cpp index 0c191fe2f7..3683622d3e 100644 --- a/servers/rendering/renderer_rd/effects_rd.cpp +++ b/servers/rendering/renderer_rd/effects_rd.cpp @@ -759,7 +759,7 @@ void EffectsRD::make_mipmap_raster(RID p_source_rd_texture, RID p_dest_framebuff RD::get_singleton()->draw_list_end(); } -void EffectsRD::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, float p_z_near, float p_z_far, bool p_dp_flip) { +void EffectsRD::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip) { CopyToDPPushConstant push_constant; push_constant.screen_rect[0] = p_rect.position.x; push_constant.screen_rect[1] = p_rect.position.y; @@ -767,7 +767,9 @@ void EffectsRD::copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffe push_constant.screen_rect[3] = p_rect.size.height; push_constant.z_far = p_z_far; push_constant.z_near = p_z_near; - push_constant.z_flip = p_dp_flip; + push_constant.texel_size[0] = 1.0f / p_dst_size.x; + push_constant.texel_size[1] = 1.0f / p_dst_size.y; + push_constant.texel_size[0] *= p_dp_flip ? -1.0f : 1.0f; // Encode dp flip as x size sign RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dst_framebuffer, RD::INITIAL_ACTION_DROP, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ); RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, cube_to_dp.pipeline.get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dst_framebuffer))); diff --git a/servers/rendering/renderer_rd/effects_rd.h b/servers/rendering/renderer_rd/effects_rd.h index 0c9b2efb7f..c8d4cb7ad4 100644 --- a/servers/rendering/renderer_rd/effects_rd.h +++ b/servers/rendering/renderer_rd/effects_rd.h @@ -321,8 +321,7 @@ private: struct CopyToDPPushConstant { float z_far; float z_near; - uint32_t z_flip; - uint32_t pad; + float texel_size[2]; float screen_rect[4]; }; @@ -770,7 +769,7 @@ public: void cubemap_roughness_raster(RID p_source_rd_texture, RID p_dest_framebuffer, uint32_t p_face_id, uint32_t p_sample_count, float p_roughness, float p_size); void make_mipmap(RID p_source_rd_texture, RID p_dest_texture, const Size2i &p_size); void make_mipmap_raster(RID p_source_rd_texture, RID p_dest_framebuffer, const Size2i &p_size); - void copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dest_texture, const Rect2 &p_rect, float p_z_near, float p_z_far, bool p_dp_flip); + void copy_cubemap_to_dp(RID p_source_rd_texture, RID p_dst_framebuffer, const Rect2 &p_rect, const Vector2 &p_dst_size, float p_z_near, float p_z_far, bool p_dp_flip); void luminance_reduction(RID p_source_texture, const Size2i p_source_size, const Vector p_reduce, RID p_prev_luminance, float p_min_luminance, float p_max_luminance, float p_adjust, bool p_set = false); void luminance_reduction_raster(RID p_source_texture, const Size2i p_source_size, const Vector p_reduce, Vector p_fb, RID p_prev_luminance, float p_min_luminance, float p_max_luminance, float p_adjust, bool p_set = false); 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; - - //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); - } - - //erase previous - shadow_atlas->quadrants[q].shadows.write[s].version = 0; - shadow_atlas->quadrants[q].shadows.write[s].owner = RID(); - - 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; - } - - //no better place for this shadow found, keep current - - //already existing, see if it should redraw or it's just OK - - shadow_atlas->quadrants[q].shadows.write[s].version = p_light_version; - - return should_redraw; + old_subdivision = shadow_atlas->quadrants[old_quadrant].subdivision; } - int new_quadrant, new_shadow; + bool is_omni = li->light_type == RS::LIGHT_OMNI; + bool found_shadow = false; + int new_quadrant = -1; + int new_shadow = -1; - //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); + 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); + } + + 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(); + } } + uint32_t new_key = new_quadrant << ShadowAtlas::QUADRANT_SHIFT; + new_key |= new_shadow; + + 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); + sh->owner = p_light_intance; sh->alloc_tick = tick; sh->version = p_light_version; + + if (is_omni) { + new_key |= ShadowAtlas::OMNI_LIGHT_FLAG; + + 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); + + extra_sh->owner = p_light_intance; + extra_sh->alloc_tick = tick; + extra_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 &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 &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 &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); } diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h index a7cc27be4a..37533baecf 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h @@ -255,7 +255,8 @@ private: struct ShadowAtlas { enum { QUADRANT_SHIFT = 27, - SHADOW_INDEX_MASK = (1 << QUADRANT_SHIFT) - 1, + OMNI_LIGHT_FLAG = 1 << 26, + SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1, SHADOW_INVALID = 0xFFFFFFFF }; @@ -299,7 +300,9 @@ private: void _update_shadow_atlas(ShadowAtlas *shadow_atlas); + void _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); bool _shadow_atlas_find_shadow(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); + bool _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); RS::ShadowQuality shadows_quality = RS::SHADOW_QUALITY_MAX; //So it always updates when first set RS::ShadowQuality directional_shadow_quality = RS::SHADOW_QUALITY_MAX; @@ -379,10 +382,6 @@ private: uint32_t cull_mask = 0; uint32_t light_directional_index = 0; - uint32_t current_shadow_atlas_key = 0; - - Vector2 dp; - Rect2 directional_rect; Set shadow_atlases; //shadow atlases where this light is registered @@ -575,7 +574,7 @@ private: struct LightData { float position[3]; float inv_radius; - float direction[3]; + float direction[3]; // in omni, x and y are used for dual paraboloid offset float size; float color[3]; @@ -964,7 +963,7 @@ public: return li->transform; } - _FORCE_INLINE_ Rect2 light_instance_get_shadow_atlas_rect(RID p_light_instance, RID p_shadow_atlas) { + _FORCE_INLINE_ Rect2 light_instance_get_shadow_atlas_rect(RID p_light_instance, RID p_shadow_atlas, Vector2i &r_omni_offset) { ShadowAtlas *shadow_atlas = shadow_atlas_owner.getornull(p_shadow_atlas); LightInstance *li = light_instance_owner.getornull(p_light_instance); uint32_t key = shadow_atlas->shadow_owners[li->self]; @@ -984,6 +983,16 @@ public: x += (shadow % shadow_atlas->quadrants[quadrant].subdivision) * shadow_size; y += (shadow / shadow_atlas->quadrants[quadrant].subdivision) * shadow_size; + if (key & ShadowAtlas::OMNI_LIGHT_FLAG) { + if (((shadow + 1) % shadow_atlas->quadrants[quadrant].subdivision) == 0) { + r_omni_offset.x = 1 - int(shadow_atlas->quadrants[quadrant].subdivision); + r_omni_offset.y = 1; + } else { + r_omni_offset.x = 1; + r_omni_offset.y = 0; + } + } + uint32_t width = shadow_size; uint32_t height = shadow_size; diff --git a/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl b/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl index dfbce29119..69b895ed29 100644 --- a/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl +++ b/servers/rendering/renderer_rd/shaders/cube_to_dp.glsl @@ -7,8 +7,7 @@ layout(push_constant, binding = 1, std430) uniform Params { float z_far; float z_near; - bool z_flip; - uint pad; + vec2 texel_size; vec4 screen_rect; } params; @@ -35,22 +34,23 @@ layout(set = 0, binding = 0) uniform samplerCube source_cube; layout(push_constant, binding = 1, std430) uniform Params { float z_far; float z_near; - bool z_flip; - uint pad; + vec2 texel_size; vec4 screen_rect; } params; void main() { vec2 uv = uv_interp; + vec2 texel_size = abs(params.texel_size); + + uv = clamp(uv * (1.0 + 2.0 * texel_size) - texel_size, vec2(0.0), vec2(1.0)); vec3 normal = vec3(uv * 2.0 - 1.0, 0.0); - - normal.z = 0.5 - 0.5 * ((normal.x * normal.x) + (normal.y * normal.y)); + normal.z = 0.5 * (1.0 - dot(normal.xy, normal.xy)); // z = 1/2 - 1/2 * (x^2 + y^2) normal = normalize(normal); normal.y = -normal.y; //needs to be flipped to match projection matrix - if (!params.z_flip) { + if (params.texel_size.x >= 0.0) { // Sign is used to encode Z flip normal.z = -normal.z; } diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl index 4f140dd10d..adf9f20618 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl @@ -1601,7 +1601,7 @@ void main() { continue; // Statically baked light and object uses lightmap, skip } - float shadow = light_process_omni_shadow(light_index, vertex, view); + float shadow = light_process_omni_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); @@ -1677,7 +1677,7 @@ void main() { continue; // Statically baked light and object uses lightmap, skip } - float shadow = light_process_spot_shadow(light_index, vertex, view); + float shadow = light_process_spot_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index 4a41c66ef3..ef2fde7516 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -279,7 +279,7 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float atte } #ifdef USE_SHADOW_TO_OPACITY - alpha = min(alpha, clamp(1.0 - attenuation), 0.0, 1.0)); + alpha = min(alpha, clamp(1.0 - attenuation, 0.0, 1.0)); #endif #endif //defined(LIGHT_CODE_USED) @@ -320,7 +320,7 @@ float sample_directional_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, ve return avg * (1.0 / float(sc_directional_soft_shadow_samples)); } -float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) { +float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec3 coord) { vec2 pos = coord.xy; float depth = coord.z; @@ -346,6 +346,49 @@ float sample_pcf_shadow(texture2D shadow, vec2 shadow_pixel_size, vec4 coord) { return avg * (1.0 / float(sc_soft_shadow_samples)); } +float sample_omni_pcf_shadow(texture2D shadow, float blur_scale, vec2 coord, vec4 uv_rect, vec2 flip_offset, float depth) { + //if only one sample is taken, take it from the center + if (sc_soft_shadow_samples == 1) { + vec2 pos = coord * 0.5 + 0.5; + pos = uv_rect.xy + pos * uv_rect.zw; + return textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(pos, depth, 1.0)); + } + + mat2 disk_rotation; + { + float r = quick_hash(gl_FragCoord.xy) * 2.0 * M_PI; + float sr = sin(r); + float cr = cos(r); + disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr)); + } + + float avg = 0.0; + vec2 offset_scale = blur_scale * 2.0 * scene_data.shadow_atlas_pixel_size / uv_rect.zw; + + for (uint i = 0; i < sc_soft_shadow_samples; i++) { + vec2 offset = offset_scale * (disk_rotation * scene_data.soft_shadow_kernel[i].xy); + vec2 sample_coord = coord + offset; + + float sample_coord_length_sqaured = dot(sample_coord, sample_coord); + bool do_flip = sample_coord_length_sqaured > 1.0; + + if (do_flip) { + float len = sqrt(sample_coord_length_sqaured); + sample_coord = sample_coord * (2.0 / len - 1.0); + } + + sample_coord = sample_coord * 0.5 + 0.5; + sample_coord = uv_rect.xy + sample_coord * uv_rect.zw; + + if (do_flip) { + sample_coord += flip_offset; + } + avg += textureProj(sampler2DShadow(shadow, shadow_sampler), vec4(sample_coord, depth, 1.0)); + } + + return avg * (1.0 / float(sc_soft_shadow_samples)); +} + float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex_scale) { //find blocker float blocker_count = 0.0; @@ -403,15 +446,21 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { #ifndef USE_NO_SHADOWS if (omni_lights.data[idx].shadow_enabled) { // there is a shadowmap + vec2 texel_size = scene_data.shadow_atlas_pixel_size; + vec4 base_uv_rect = omni_lights.data[idx].atlas_rect; + base_uv_rect.xy += texel_size; + base_uv_rect.zw -= texel_size * 2.0; - vec3 light_rel_vec = omni_lights.data[idx].position - vertex; - float light_length = length(light_rel_vec); + // Omni lights use direction.xy to store to store the offset between the two paraboloid regions + vec2 flip_offset = omni_lights.data[idx].direction.xy; - vec4 v = vec4(vertex, 1.0); + vec3 local_vert = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz; - vec4 splane = (omni_lights.data[idx].shadow_matrix * v); + float shadow_len = length(local_vert); //need to remember shadow len from here + vec3 shadow_dir = normalize(local_vert); - float shadow_len = length(splane.xyz); //need to remember shadow len from here + vec3 local_normal = normalize(mat3(omni_lights.data[idx].shadow_matrix) * normal); + vec3 normal_bias = local_normal * omni_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(local_normal, shadow_dir))); float shadow; @@ -431,10 +480,10 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { disk_rotation = mat2(vec2(cr, -sr), vec2(sr, cr)); } - vec3 normal = normalize(splane.xyz); - vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); - vec3 tangent = normalize(cross(v0, normal)); - vec3 bitangent = normalize(cross(tangent, normal)); + vec3 basis_normal = shadow_dir; + vec3 v0 = abs(basis_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(v0, basis_normal)); + vec3 bitangent = normalize(cross(tangent, basis_normal)); float z_norm = shadow_len * omni_lights.data[idx].inv_radius; tangent *= omni_lights.data[idx].soft_shadow_size * omni_lights.data[idx].soft_shadow_scale; @@ -443,18 +492,17 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { for (uint i = 0; i < sc_penumbra_shadow_samples; i++) { vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy; - vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y; + vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y; pos = normalize(pos); - vec4 uv_rect = omni_lights.data[idx].atlas_rect; + + vec4 uv_rect = base_uv_rect; if (pos.z >= 0.0) { - pos.z += 1.0; - uv_rect.y += uv_rect.w; - } else { - pos.z = 1.0 - pos.z; + uv_rect.xy += flip_offset; } + pos.z = 1.0 + abs(pos.z); pos.xy /= pos.z; pos.xy = pos.xy * 0.5 + 0.5; @@ -479,18 +527,18 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { shadow = 0.0; for (uint i = 0; i < sc_penumbra_shadow_samples; i++) { vec2 disk = disk_rotation * scene_data.penumbra_shadow_kernel[i].xy; - vec3 pos = splane.xyz + tangent * disk.x + bitangent * disk.y; + vec3 pos = local_vert + tangent * disk.x + bitangent * disk.y; pos = normalize(pos); - vec4 uv_rect = omni_lights.data[idx].atlas_rect; + pos = normalize(pos + normal_bias); + + vec4 uv_rect = base_uv_rect; if (pos.z >= 0.0) { - pos.z += 1.0; - uv_rect.y += uv_rect.w; - } else { - pos.z = 1.0 - pos.z; + uv_rect.xy += flip_offset; } + pos.z = 1.0 + abs(pos.z); pos.xy /= pos.z; pos.xy = pos.xy * 0.5 + 0.5; @@ -505,26 +553,19 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) { shadow = 1.0; } } else { - splane.xyz = normalize(splane.xyz); - vec4 clamp_rect = omni_lights.data[idx].atlas_rect; + vec4 uv_rect = base_uv_rect; - if (splane.z >= 0.0) { - splane.z += 1.0; - - clamp_rect.y += clamp_rect.w; - - } else { - splane.z = 1.0 - splane.z; + vec3 shadow_sample = normalize(shadow_dir + normal_bias); + if (shadow_sample.z >= 0.0) { + uv_rect.xy += flip_offset; + flip_offset *= -1.0; } - splane.xy /= splane.z; - - splane.xy = splane.xy * 0.5 + 0.5; - splane.z = shadow_len * omni_lights.data[idx].inv_radius; - splane.z -= omni_lights.data[idx].shadow_bias; - splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw; - splane.w = 1.0; //needed? i think it should be 1 already - shadow = sample_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, splane); + shadow_sample.z = 1.0 + abs(shadow_sample.z); + vec2 pos = shadow_sample.xy / shadow_sample.z; + float depth = shadow_len - omni_lights.data[idx].shadow_bias; + depth *= omni_lights.data[idx].inv_radius; + shadow = sample_omni_pcf_shadow(shadow_atlas, omni_lights.data[idx].soft_shadow_scale / shadow_sample.z, pos, uv_rect, flip_offset, depth); } return shadow; @@ -608,13 +649,11 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v vec4 atlas_rect = omni_lights.data[idx].projector_rect; if (local_v.z >= 0.0) { - local_v.z += 1.0; atlas_rect.y += atlas_rect.w; - - } else { - local_v.z = 1.0 - local_v.z; } + local_v.z = 1.0 + abs(local_v.z); + local_v.xy /= local_v.z; local_v.xy = local_v.xy * 0.5 + 0.5; vec2 proj_uv = local_v.xy * atlas_rect.zw; @@ -694,15 +733,18 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) { vec3 light_rel_vec = spot_lights.data[idx].position - vertex; float light_length = length(light_rel_vec); vec3 spot_dir = spot_lights.data[idx].direction; - //there is a shadowmap - vec4 v = vec4(vertex, 1.0); - float shadow; + vec3 shadow_dir = light_rel_vec / light_length; + vec3 normal_bias = normal * light_length * spot_lights.data[idx].shadow_normal_bias * (1.0 - abs(dot(normal, shadow_dir))); + + //there is a shadowmap + vec4 v = vec4(vertex + normal_bias, 1.0); vec4 splane = (spot_lights.data[idx].shadow_matrix * v); + splane.z -= spot_lights.data[idx].shadow_bias / (light_length * spot_lights.data[idx].inv_radius); splane /= splane.w; - splane.z -= spot_lights.data[idx].shadow_bias; + float shadow; if (sc_use_light_soft_shadows && spot_lights.data[idx].soft_shadow_size > 0.0) { //soft shadow @@ -753,11 +795,9 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) { //no blockers found, so no shadow shadow = 1.0; } - } else { //hard shadow - vec4 shadow_uv = vec4(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z, 1.0); - + vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z); shadow = sample_pcf_shadow(shadow_atlas, spot_lights.data[idx].soft_shadow_scale * scene_data.shadow_atlas_pixel_size, shadow_uv); } diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl index 6e104e1203..a2a54a0511 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl @@ -1387,7 +1387,7 @@ void main() { break; } - float shadow = light_process_omni_shadow(light_index, vertex, view); + float shadow = light_process_omni_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); @@ -1435,7 +1435,7 @@ void main() { break; } - float shadow = light_process_spot_shadow(light_index, vertex, view); + float shadow = light_process_spot_shadow(light_index, vertex, normal); shadow = blur_shadow(shadow); diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 4a4976718c..cd8014632d 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -2180,8 +2180,6 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons p_scenario->indexers[Scenario::INDEXER_GEOMETRY].convex_query(planes.ptr(), planes.size(), points.ptr(), points.size(), cull_convex); - Plane near_plane(light_transform.origin, light_transform.basis.get_axis(2) * z); - RendererSceneRender::RenderShadowData &shadow_data = render_shadow_data[max_shadows_used++]; for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) { @@ -2215,7 +2213,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons real_t radius = RSG::storage->light_get_param(p_instance->base, RS::LIGHT_PARAM_RANGE); CameraMatrix cm; - cm.set_perspective(90, 1, 0.01, radius); + cm.set_perspective(90, 1, radius * 0.005f, radius); for (int i = 0; i < 6; i++) { RENDER_TIMESTAMP("Culling Shadow Cube side" + itos(i)); @@ -2301,7 +2299,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons real_t angle = RSG::storage->light_get_param(p_instance->base, RS::LIGHT_PARAM_SPOT_ANGLE); CameraMatrix cm; - cm.set_perspective(angle * 2.0, 1.0, 0.01, radius); + cm.set_perspective(angle * 2.0, 1.0, 0.005f * radius, radius); Vector planes = cm.get_projection_planes(light_transform); @@ -2794,12 +2792,9 @@ void RendererSceneCull::_render_scene(const RendererSceneRender::CameraData *p_c //rasterizer->set_camera(p_camera_data->main_transform, p_camera_data.main_projection, p_camera_data.is_ortogonal); - Vector planes = p_camera_data->main_projection.get_projection_planes(p_camera_data->main_transform); - - Plane near_plane(p_camera_data->main_transform.origin, -p_camera_data->main_transform.basis.get_axis(2).normalized()); - /* STEP 2 - CULL */ + Vector planes = p_camera_data->main_projection.get_projection_planes(p_camera_data->main_transform); cull.frustum = Frustum(planes); Vector directional_lights;