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.
This commit is contained in:
jfons 2021-08-04 17:18:06 +02:00
parent 8b6c168b3a
commit 55e7832d7b
12 changed files with 308 additions and 172 deletions

View file

@ -36,7 +36,7 @@
<member name="directional_shadow_split_3" type="float" setter="set_param" getter="get_param" default="0.5">
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].
</member>
<member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" override="true" default="1.0" />
<member name="shadow_bias" type="float" setter="set_param" getter="get_param" override="true" default="0.1" />
<member name="use_in_sky_only" type="bool" setter="set_sky_only" getter="is_sky_only" default="false">
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.
</member>

View file

@ -61,7 +61,7 @@
<member name="light_specular" type="float" setter="set_param" getter="get_param" default="0.5">
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.
</member>
<member name="shadow_bias" type="float" setter="set_param" getter="get_param" default="0.1">
<member name="shadow_bias" type="float" setter="set_param" getter="get_param" default="0.2">
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.
</member>
<member name="shadow_blur" type="float" setter="set_param" getter="get_param" default="1.0">
@ -75,7 +75,7 @@
</member>
<member name="shadow_fog_fade" type="float" setter="set_param" getter="get_param" default="0.1">
</member>
<member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="2.0">
<member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="1.0">
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.
</member>
<member name="shadow_reverse_cull_face" type="bool" setter="set_shadow_reverse_cull_face" getter="get_shadow_reverse_cull_face" default="false">

View file

@ -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<String> SpotLight3D::get_configuration_warnings() const {

View file

@ -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)));

View file

@ -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<RID> 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<RID> p_reduce, Vector<RID> p_fb, RID p_prev_luminance, float p_min_luminance, float p_max_luminance, float p_adjust, bool p_set = false);

View file

@ -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<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);
}

View file

@ -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<RID> 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;

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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<Plane> 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<Plane> 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<Plane> planes = p_camera_data->main_projection.get_projection_planes(p_camera_data->main_transform);
cull.frustum = Frustum(planes);
Vector<RID> directional_lights;