diff --git a/doc/classes/DirectionalLight.xml b/doc/classes/DirectionalLight.xml
index 8496d3f19e9..6782c90efe9 100644
--- a/doc/classes/DirectionalLight.xml
+++ b/doc/classes/DirectionalLight.xml
@@ -21,6 +21,9 @@
Optimizes shadow rendering for detail versus movement. See [enum ShadowDepthRange].
+
+ Proportion of [member directional_shadow_max_distance] at which point the shadow starts to fade. At [member directional_shadow_max_distance], the shadow will disappear. The default value is a balance between smooth fading and distant shadow visibility. If the camera moves fast and the [member directional_shadow_max_distance] is low, consider lowering [member directional_shadow_fade_start] below [code]0.8[/code] to make shadow transitions less noticeable. On the other hand, if you tuned [member directional_shadow_max_distance] to cover the entire scene, you can set [member directional_shadow_fade_start] to [code]1.0[/code] to prevent the shadow from fading in the distance (it will suddenly cut off instead).
+
The maximum distance for shadow splits. Increasing this value will make directional shadows visible from further away, at the cost of lower overall shadow detail and performance (since more objects need to be included in the directional shadow rendering).
diff --git a/doc/classes/Light.xml b/doc/classes/Light.xml
index 7cfa11ad409..6051311fa27 100644
--- a/doc/classes/Light.xml
+++ b/doc/classes/Light.xml
@@ -122,7 +122,10 @@
Constant for accessing [member DirectionalLight.directional_shadow_bias_split_scale].
-
+
+ Constant for accessing [member DirectionalLight.directional_shadow_fade_start].
+
+
Represents the size of the [enum Param] enum.
diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml
index bcc4fe0f44a..e7091326b71 100644
--- a/doc/classes/VisualServer.xml
+++ b/doc/classes/VisualServer.xml
@@ -3648,7 +3648,10 @@
Increases bias on further splits to fix self-shadowing that only occurs far away from the camera.
-
+
+ Proportion of [constant LIGHT_PARAM_SHADOW_MAX_DISTANCE] at which point the shadow starts to fade. At [constant LIGHT_PARAM_SHADOW_MAX_DISTANCE], the shadow will disappear. The default value is a balance between smooth fading and distant shadow visibility. If the camera moves fast and the [constant LIGHT_PARAM_SHADOW_MAX_DISTANCE] is low, consider lowering [constant LIGHT_PARAM_SHADOW_FADE_START] below [code]0.8[/code] to make shadow transitions less noticeable. On the other hand, if you tuned [constant LIGHT_PARAM_SHADOW_MAX_DISTANCE] to cover the entire scene, you can set [constant LIGHT_PARAM_SHADOW_FADE_START] to [code]1.0[/code] to prevent the shadow from fading in the distance (it will suddenly cut off instead).
+
+
Represents the size of the [enum LightParam] enum.
diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp
index 08a45242853..cfb9e593cab 100644
--- a/drivers/gles2/rasterizer_scene_gles2.cpp
+++ b/drivers/gles2/rasterizer_scene_gles2.cpp
@@ -2087,6 +2087,12 @@ void RasterizerSceneGLES2::_setup_light(LightInstance *light, ShadowAtlas *shado
// state.scene_shader.set_uniform(SceneShaderGLES2::LIGHT_CLAMP, light_clamp);
state.scene_shader.set_uniform(SceneShaderGLES2::SHADOW_PIXEL_SIZE, Size2(1.0 / directional_shadow.size, 1.0 / directional_shadow.size));
state.scene_shader.set_uniform(SceneShaderGLES2::LIGHT_SPLIT_OFFSETS, split_offsets);
+
+ const float fade_start = light_ptr->param[VS::LIGHT_PARAM_SHADOW_FADE_START];
+ // Using 1.0 would break `smoothstep()` in the shader.
+ state.scene_shader.set_uniform(SceneShaderGLES2::FADE_FROM, -split_offsets[shadow_count - 1] * MIN(fade_start, 0.999));
+ state.scene_shader.set_uniform(SceneShaderGLES2::FADE_TO, -split_offsets[shadow_count - 1]);
+
state.scene_shader.set_uniform(SceneShaderGLES2::LIGHT_SHADOW_MATRIX, matrices[0]);
state.scene_shader.set_uniform(SceneShaderGLES2::LIGHT_SHADOW_MATRIX2, matrices[1]);
state.scene_shader.set_uniform(SceneShaderGLES2::LIGHT_SHADOW_MATRIX3, matrices[2]);
diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp
index 11d8fd815f7..2b58d228c70 100644
--- a/drivers/gles2/rasterizer_storage_gles2.cpp
+++ b/drivers/gles2/rasterizer_storage_gles2.cpp
@@ -4233,6 +4233,7 @@ RID RasterizerStorageGLES2::light_create(VS::LightType p_type) {
light->param[VS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET] = 0.6;
light->param[VS::LIGHT_PARAM_SHADOW_NORMAL_BIAS] = 0.1;
light->param[VS::LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE] = 0.1;
+ light->param[VS::LIGHT_PARAM_SHADOW_FADE_START] = 0.8;
light->color = Color(1, 1, 1, 1);
light->shadow = false;
@@ -4270,7 +4271,8 @@ void RasterizerStorageGLES2::light_set_param(RID p_light, VS::LightParam p_param
case VS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET:
case VS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET:
case VS::LIGHT_PARAM_SHADOW_NORMAL_BIAS:
- case VS::LIGHT_PARAM_SHADOW_BIAS: {
+ case VS::LIGHT_PARAM_SHADOW_BIAS:
+ case VS::LIGHT_PARAM_SHADOW_FADE_START: {
light->version++;
light->instance_change_notify(true, false);
} break;
diff --git a/drivers/gles2/shaders/scene.glsl b/drivers/gles2/shaders/scene.glsl
index 961940c2610..d9f9cccd038 100644
--- a/drivers/gles2/shaders/scene.glsl
+++ b/drivers/gles2/shaders/scene.glsl
@@ -1104,6 +1104,8 @@ uniform highp sampler2D light_shadow_atlas; //texunit:-3
#ifdef LIGHT_MODE_DIRECTIONAL
uniform highp sampler2D light_directional_shadow; // texunit:-3
uniform highp vec4 light_split_offsets;
+uniform mediump float fade_from;
+uniform mediump float fade_to;
#endif
varying highp vec4 shadow_coord;
@@ -1987,7 +1989,6 @@ FRAGMENT_SHADER_CODE
float shadow4 = sample_shadow(light_directional_shadow, shadow_coord4);
if (depth_z < light_split_offsets.w) {
- float pssm_fade = 0.0;
float shadow_att = 1.0;
#ifdef LIGHT_USE_PSSM_BLEND
float shadow_att2 = 1.0;
@@ -2023,7 +2024,6 @@ FRAGMENT_SHADER_CODE
} else {
shadow_att = shadow4;
- pssm_fade = smoothstep(light_split_offsets.z, light_split_offsets.w, depth_z);
#if defined(LIGHT_USE_PSSM_BLEND)
use_blend = false;
@@ -2047,7 +2047,6 @@ FRAGMENT_SHADER_CODE
float shadow3 = sample_shadow(light_directional_shadow, shadow_coord3);
if (depth_z < light_split_offsets.z) {
- float pssm_fade = 0.0;
float shadow_att = 1.0;
#ifdef LIGHT_USE_PSSM_BLEND
float shadow_att2 = 1.0;
@@ -2097,7 +2096,6 @@ FRAGMENT_SHADER_CODE
if (depth_z < light_split_offsets.y) {
float shadow_att = 1.0;
- float pssm_fade = 0.0;
#ifdef LIGHT_USE_PSSM_BLEND
float shadow_att2 = 1.0;
@@ -2105,7 +2103,6 @@ FRAGMENT_SHADER_CODE
bool use_blend = true;
#endif
if (depth_z < light_split_offsets.x) {
- float pssm_fade = 0.0;
shadow_att = shadow1;
#ifdef LIGHT_USE_PSSM_BLEND
@@ -2114,7 +2111,6 @@ FRAGMENT_SHADER_CODE
#endif
} else {
shadow_att = shadow2;
- pssm_fade = smoothstep(light_split_offsets.x, light_split_offsets.y, depth_z);
#ifdef LIGHT_USE_PSSM_BLEND
use_blend = false;
#endif
@@ -2148,7 +2144,6 @@ FRAGMENT_SHADER_CODE
#endif //pssm2
highp vec4 pssm_coord;
- float pssm_fade = 0.0;
#ifdef LIGHT_USE_PSSM_BLEND
float pssm_blend;
@@ -2187,7 +2182,6 @@ FRAGMENT_SHADER_CODE
} else {
pssm_coord = shadow_coord4;
- pssm_fade = smoothstep(light_split_offsets.z, light_split_offsets.w, depth_z);
#if defined(LIGHT_USE_PSSM_BLEND)
use_blend = false;
@@ -2217,7 +2211,6 @@ FRAGMENT_SHADER_CODE
}
} else {
pssm_coord = shadow_coord3;
- pssm_fade = smoothstep(light_split_offsets.y, light_split_offsets.z, depth_z);
#if defined(LIGHT_USE_PSSM_BLEND)
use_blend = false;
@@ -2236,7 +2229,6 @@ FRAGMENT_SHADER_CODE
#endif
} else {
pssm_coord = shadow_coord2;
- pssm_fade = smoothstep(light_split_offsets.x, light_split_offsets.y, depth_z);
#ifdef LIGHT_USE_PSSM_BLEND
use_blend = false;
#endif
@@ -2258,7 +2250,8 @@ FRAGMENT_SHADER_CODE
}
#endif
- light_att *= mix(shadow_color.rgb, vec3(1.0), shadow);
+ float pssm_fade = smoothstep(fade_from, fade_to, vertex.z);
+ light_att *= mix(mix(shadow_color.rgb, vec3(1.0), shadow), vec3(1.0), pssm_fade);
}
}
#endif //use vertex lighting
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 3093973979d..b514a580fae 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -2766,6 +2766,7 @@ void RasterizerSceneGLES3::_setup_directional_light(int p_index, const Transform
}
}
+ // Store the fade distance factor relative to the last split's end.
ubo_data.shadow_split_offsets[j] = li->shadow_transform[j].split;
Transform modelview = (p_camera_inverse_transform * li->shadow_transform[j].transform).affine_inverse();
@@ -2785,6 +2786,11 @@ void RasterizerSceneGLES3::_setup_directional_light(int p_index, const Transform
ubo_data.light_clamp[2] = atlas_rect.size.x;
ubo_data.light_clamp[3] = atlas_rect.size.y;
}
+
+ const float fade_start = li->light_ptr->param[VS::LIGHT_PARAM_SHADOW_FADE_START];
+ // Using 1.0 would break `smoothstep()` in the shader.
+ ubo_data.fade_from = -ubo_data.shadow_split_offsets[shadow_count - 1] * MIN(fade_start, 0.999);
+ ubo_data.fade_to = -ubo_data.shadow_split_offsets[shadow_count - 1];
}
glBindBuffer(GL_UNIFORM_BUFFER, state.directional_ubo);
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index 4342d492677..0463e594868 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -587,6 +587,10 @@ public:
float matrix[4 * 16];
} shadow;
float shadow_split_offsets[4];
+
+ float fade_from;
+ float fade_to;
+ float pad[2];
};
struct LightInstance : public RID_Data {
diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp
index 04da4002430..6fdd8c466d7 100644
--- a/drivers/gles3/rasterizer_storage_gles3.cpp
+++ b/drivers/gles3/rasterizer_storage_gles3.cpp
@@ -5450,6 +5450,7 @@ RID RasterizerStorageGLES3::light_create(VS::LightType p_type) {
light->param[VS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET] = 0.6;
light->param[VS::LIGHT_PARAM_SHADOW_NORMAL_BIAS] = 0.1;
light->param[VS::LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE] = 0.1;
+ light->param[VS::LIGHT_PARAM_SHADOW_FADE_START] = 0.8;
light->color = Color(1, 1, 1, 1);
light->shadow = false;
@@ -5486,7 +5487,8 @@ void RasterizerStorageGLES3::light_set_param(RID p_light, VS::LightParam p_param
case VS::LIGHT_PARAM_SHADOW_SPLIT_2_OFFSET:
case VS::LIGHT_PARAM_SHADOW_SPLIT_3_OFFSET:
case VS::LIGHT_PARAM_SHADOW_NORMAL_BIAS:
- case VS::LIGHT_PARAM_SHADOW_BIAS: {
+ case VS::LIGHT_PARAM_SHADOW_BIAS:
+ case VS::LIGHT_PARAM_SHADOW_FADE_START: {
light->version++;
light->instance_change_notify(true, false);
} break;
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index a4d35a79b96..dd22d99cdbc 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -146,6 +146,10 @@ layout(std140) uniform DirectionalLightData { //ubo:3
highp mat4 shadow_matrix3;
highp mat4 shadow_matrix4;
mediump vec4 shadow_split_offsets;
+
+ mediump float fade_from;
+ mediump float fade_to;
+ mediump vec2 pad;
};
#endif //ubershader-skip
@@ -842,6 +846,10 @@ layout(std140) uniform DirectionalLightData {
highp mat4 shadow_matrix3;
highp mat4 shadow_matrix4;
mediump vec4 shadow_split_offsets;
+
+ mediump float fade_from;
+ mediump float fade_to;
+ mediump vec2 pad;
};
uniform highp sampler2DShadow directional_shadow; // texunit:-5
@@ -2141,7 +2149,6 @@ FRAGMENT_SHADER_CODE
#endif //LIGHT_USE_PSSM4 //ubershader-runtime
if (depth_z < value) {
vec3 pssm_coord;
- float pssm_fade = 0.0;
#ifdef LIGHT_USE_PSSM_BLEND //ubershader-skip
float pssm_blend;
@@ -2187,7 +2194,6 @@ FRAGMENT_SHADER_CODE
} else {
highp vec4 splane = (shadow_matrix4 * vec4(vertex, 1.0));
pssm_coord = splane.xyz / splane.w;
- pssm_fade = smoothstep(shadow_split_offsets.z, shadow_split_offsets.w, depth_z);
#ifdef LIGHT_USE_PSSM_BLEND //ubershader-runtime
use_blend = false;
@@ -2225,7 +2231,6 @@ FRAGMENT_SHADER_CODE
} else {
highp vec4 splane = (shadow_matrix3 * vec4(vertex, 1.0));
pssm_coord = splane.xyz / splane.w;
- pssm_fade = smoothstep(shadow_split_offsets.y, shadow_split_offsets.z, depth_z);
#ifdef LIGHT_USE_PSSM_BLEND //ubershader-runtime
use_blend = false;
@@ -2250,7 +2255,6 @@ FRAGMENT_SHADER_CODE
} else {
highp vec4 splane = (shadow_matrix2 * vec4(vertex, 1.0));
pssm_coord = splane.xyz / splane.w;
- pssm_fade = smoothstep(shadow_split_offsets.x, shadow_split_offsets.y, depth_z);
#ifdef LIGHT_USE_PSSM_BLEND //ubershader-runtime
use_blend = false;
@@ -2287,6 +2291,7 @@ FRAGMENT_SHADER_CODE
shadow = min(shadow, contact_shadow);
}
#endif //ubershader-runtime
+ float pssm_fade = smoothstep(fade_from, fade_to, vertex.z);
light_attenuation = mix(mix(shadow_color_contact.rgb, vec3(1.0), shadow), vec3(1.0), pssm_fade);
}
diff --git a/scene/3d/light.cpp b/scene/3d/light.cpp
index e0d21bca4e6..8370b443485 100644
--- a/scene/3d/light.cpp
+++ b/scene/3d/light.cpp
@@ -267,6 +267,7 @@ void Light::_bind_methods() {
BIND_ENUM_CONSTANT(PARAM_SHADOW_NORMAL_BIAS);
BIND_ENUM_CONSTANT(PARAM_SHADOW_BIAS);
BIND_ENUM_CONSTANT(PARAM_SHADOW_BIAS_SPLIT_SCALE);
+ BIND_ENUM_CONSTANT(PARAM_SHADOW_FADE_START);
BIND_ENUM_CONSTANT(PARAM_MAX);
BIND_ENUM_CONSTANT(BAKE_DISABLED);
@@ -314,6 +315,7 @@ Light::Light(VisualServer::LightType p_type) {
set_param(PARAM_SHADOW_SPLIT_1_OFFSET, 0.1);
set_param(PARAM_SHADOW_SPLIT_2_OFFSET, 0.2);
set_param(PARAM_SHADOW_SPLIT_3_OFFSET, 0.5);
+ set_param(PARAM_SHADOW_FADE_START, 0.8);
set_param(PARAM_SHADOW_NORMAL_BIAS, 0.0);
set_param(PARAM_SHADOW_BIAS, 0.15);
set_disable_scale(true);
@@ -391,6 +393,7 @@ void DirectionalLight::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_split_2", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_2_OFFSET);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_split_3", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_SPLIT_3_OFFSET);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional_shadow_blend_splits"), "set_blend_splits", "is_blend_splits_enabled");
+ ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_fade_start", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_FADE_START);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_normal_bias", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_NORMAL_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::REAL, "directional_shadow_bias_split_scale", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_param", "get_param", PARAM_SHADOW_BIAS_SPLIT_SCALE);
ADD_PROPERTY(PropertyInfo(Variant::INT, "directional_shadow_depth_range", PROPERTY_HINT_ENUM, "Stable,Optimized"), "set_shadow_depth_range", "get_shadow_depth_range");
@@ -411,6 +414,7 @@ DirectionalLight::DirectionalLight() :
set_param(PARAM_SHADOW_BIAS, 0.1);
set_param(PARAM_SHADOW_MAX_DISTANCE, 100);
set_param(PARAM_SHADOW_BIAS_SPLIT_SCALE, 0.25);
+ set_param(PARAM_SHADOW_FADE_START, 0.8);
set_shadow_mode(SHADOW_PARALLEL_4_SPLITS);
set_shadow_depth_range(SHADOW_DEPTH_RANGE_STABLE);
diff --git a/scene/3d/light.h b/scene/3d/light.h
index fbce9f678c0..9ad3f37b4a9 100644
--- a/scene/3d/light.h
+++ b/scene/3d/light.h
@@ -57,6 +57,7 @@ public:
PARAM_SHADOW_NORMAL_BIAS = VS::LIGHT_PARAM_SHADOW_NORMAL_BIAS,
PARAM_SHADOW_BIAS = VS::LIGHT_PARAM_SHADOW_BIAS,
PARAM_SHADOW_BIAS_SPLIT_SCALE = VS::LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE,
+ PARAM_SHADOW_FADE_START = VS::LIGHT_PARAM_SHADOW_FADE_START,
PARAM_MAX = VS::LIGHT_PARAM_MAX
};
diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp
index 447ca596d97..66c162b4c68 100644
--- a/servers/visual_server.cpp
+++ b/servers/visual_server.cpp
@@ -2381,6 +2381,7 @@ void VisualServer::_bind_methods() {
BIND_ENUM_CONSTANT(LIGHT_PARAM_SHADOW_NORMAL_BIAS);
BIND_ENUM_CONSTANT(LIGHT_PARAM_SHADOW_BIAS);
BIND_ENUM_CONSTANT(LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE);
+ BIND_ENUM_CONSTANT(LIGHT_PARAM_SHADOW_FADE_START);
BIND_ENUM_CONSTANT(LIGHT_PARAM_MAX);
BIND_ENUM_CONSTANT(LIGHT_BAKE_DISABLED);
diff --git a/servers/visual_server.h b/servers/visual_server.h
index bb8fc9010d9..6034f1b80d5 100644
--- a/servers/visual_server.h
+++ b/servers/visual_server.h
@@ -448,6 +448,7 @@ public:
LIGHT_PARAM_SHADOW_NORMAL_BIAS,
LIGHT_PARAM_SHADOW_BIAS,
LIGHT_PARAM_SHADOW_BIAS_SPLIT_SCALE,
+ LIGHT_PARAM_SHADOW_FADE_START,
LIGHT_PARAM_MAX
};