Skip to content

Commit

Permalink
Implement distance fade properties in OmniLight3D and SpotLight3D
Browse files Browse the repository at this point in the history
This can be used to fade lights and their shadows in the distance,
similar to Decal nodes. This can bring significant performance
improvements, especially for lights with shadows enabled and when
using higher-than-default shadow quality settings.

While lights can be smoothly faded out over distance, shadows are
currently "all or nothing" since per-light shadow color is no longer
customizable in the Vulkan renderer. This may result in noticeable
pop-in when leaving the shadow cutoff distance, but depending on the
scene, it may not always be that noticeable.
  • Loading branch information
Calinou committed Feb 25, 2022
1 parent 4dc8214 commit b1a295b
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 6 deletions.
19 changes: 18 additions & 1 deletion doc/classes/Light3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@
</method>
</methods>
<members>
<member name="distance_fade_begin" type="float" setter="set_distance_fade_begin" getter="get_distance_fade_begin" default="40.0">
The distance from the camera at which the light begins to fade away (in 3D units).
[b]Note:[/b] Only effective for [OmniLight3D] and [SpotLight3D].
</member>
<member name="distance_fade_enabled" type="bool" setter="set_enable_distance_fade" getter="is_distance_fade_enabled" default="false">
If [code]true[/code], the light will smoothly fade away when far from the active [Camera3D] starting at [member distance_fade_begin]. This acts as a form of level of detail (LOD). The light will fade out over [member distance_fade_begin] + [member distance_fade_length], after which it will be culled and not sent to the shader at all. Use this to reduce the number of active lights in a scene and thus improve performance.
[b]Note:[/b] Only effective for [OmniLight3D] and [SpotLight3D].
</member>
<member name="distance_fade_length" type="float" setter="set_distance_fade_length" getter="get_distance_fade_length" default="10.0">
Distance over which the light fades. The light's energy is progressively reduced over this distance and is completely invisible at the end.
[b]Note:[/b] Only effective for [OmniLight3D] and [SpotLight3D].
</member>
<member name="distance_fade_shadow" type="float" setter="set_distance_fade_shadow" getter="get_distance_fade_shadow" default="50.0">
The distance from the camera at which the light's shadow cuts off (in 3D units). Set this to a value lower than [member distance_fade_begin] + [member distance_fade_length] to further improve performance, as shadow rendering is often more expensive than light rendering itself.
[b]Note:[/b] Only effective for [OmniLight3D] and [SpotLight3D], and only when [member shadow_enabled] is [code]true[/code].
[b]Note:[/b] Due to a rendering engine limitation, shadows will be disabled instantly instead of fading smoothly according to [member distance_fade_length]. This may result in visible pop-in depending on the scene topography.
</member>
<member name="editor_only" type="bool" setter="set_editor_only" getter="is_editor_only" default="false">
If [code]true[/code], the light only appears in the editor and will not be visible at runtime.
</member>
Expand Down Expand Up @@ -73,7 +90,7 @@
The color of shadows cast by this light.
</member>
<member name="shadow_enabled" type="bool" setter="set_shadow" getter="has_shadow" default="false">
If [code]true[/code], the light will cast shadows.
If [code]true[/code], the light will cast real-time shadows. This has a significant performance cost. Only enable shadow rendering when it makes a noticeable difference in the scene's appearance, and consider using [member distance_fade_enabled] to hide the light when far away from the [Camera3D].
</member>
<member name="shadow_fog_fade" type="float" setter="set_param" getter="get_param" default="0.1">
</member>
Expand Down
11 changes: 11 additions & 0 deletions doc/classes/RenderingServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1650,6 +1650,17 @@
Sets the cull mask for this Light3D. Lights only affect objects in the selected layers. Equivalent to [member Light3D.light_cull_mask].
</description>
</method>
<method name="light_set_distance_fade">
<return type="void" />
<argument index="0" name="decal" type="RID" />
<argument index="1" name="enabled" type="bool" />
<argument index="2" name="begin" type="float" />
<argument index="3" name="shadow" type="float" />
<argument index="4" name="length" type="float" />
<description>
Sets the distance fade for this Light3D. This acts as a form of level of detail (LOD) and can be used to improve performance. Equivalent to [member Light3D.distance_fade_enabled], [member Light3D.distance_fade_begin], [member Light3D.distance_fade_shadow], and [member Light3D.distance_fade_length].
</description>
</method>
<method name="light_set_max_sdfgi_cascade">
<return type="void" />
<argument index="0" name="light" type="RID" />
Expand Down
3 changes: 3 additions & 0 deletions drivers/gles3/rasterizer_storage_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,9 @@ void RasterizerStorageGLES3::light_set_negative(RID p_light, bool p_enable) {
void RasterizerStorageGLES3::light_set_cull_mask(RID p_light, uint32_t p_mask) {
}

void RasterizerStorageGLES3::light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) {
}

void RasterizerStorageGLES3::light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) {
}

Expand Down
1 change: 1 addition & 0 deletions drivers/gles3/rasterizer_storage_gles3.h
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,7 @@ class RasterizerStorageGLES3 : public RendererStorage {
void light_set_projector(RID p_light, RID p_texture) override;
void light_set_negative(RID p_light, bool p_enable) override;
void light_set_cull_mask(RID p_light, uint32_t p_mask) override;
void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override;
void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override;
void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override;
void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override;
Expand Down
67 changes: 66 additions & 1 deletion scene/3d/light_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,43 @@ bool Light3D::is_negative() const {
return negative;
}

void Light3D::set_enable_distance_fade(bool p_enable) {
distance_fade_enabled = p_enable;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
notify_property_list_changed();
}

bool Light3D::is_distance_fade_enabled() const {
return distance_fade_enabled;
}

void Light3D::set_distance_fade_begin(real_t p_distance) {
distance_fade_begin = p_distance;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
}

real_t Light3D::get_distance_fade_begin() const {
return distance_fade_begin;
}

void Light3D::set_distance_fade_shadow(real_t p_distance) {
distance_fade_shadow = p_distance;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
}

real_t Light3D::get_distance_fade_shadow() const {
return distance_fade_shadow;
}

void Light3D::set_distance_fade_length(real_t p_length) {
distance_fade_length = p_length;
RS::get_singleton()->light_set_distance_fade(light, distance_fade_enabled, distance_fade_begin, distance_fade_shadow, distance_fade_length);
}

real_t Light3D::get_distance_fade_length() const {
return distance_fade_length;
}

void Light3D::set_cull_mask(uint32_t p_cull_mask) {
cull_mask = p_cull_mask;
RS::get_singleton()->light_set_cull_mask(light, p_cull_mask);
Expand Down Expand Up @@ -195,14 +232,19 @@ bool Light3D::is_editor_only() const {
}

void Light3D::_validate_property(PropertyInfo &property) const {
if (!shadow && (property.name == "shadow_color" || property.name == "shadow_bias" || property.name == "shadow_normal_bias" || property.name == "shadow_reverse_cull_face" || property.name == "shadow_transmittance_bias" || property.name == "shadow_fog_fade" || property.name == "shadow_blur")) {
if (!shadow && (property.name == "shadow_color" || property.name == "shadow_bias" || property.name == "shadow_normal_bias" || property.name == "shadow_reverse_cull_face" || property.name == "shadow_transmittance_bias" || property.name == "shadow_fog_fade" || property.name == "shadow_blur" || property.name == "distance_fade_shadow")) {
property.usage = PROPERTY_USAGE_NO_EDITOR;
}

if (get_light_type() != RS::LIGHT_DIRECTIONAL && property.name == "light_angular_distance") {
// Angular distance is only used in DirectionalLight3D.
property.usage = PROPERTY_USAGE_NONE;
}

if (!distance_fade_enabled && (property.name == "distance_fade_begin" || property.name == "distance_fade_shadow" || property.name == "distance_fade_length")) {
property.usage = PROPERTY_USAGE_NO_EDITOR;
}

VisualInstance3D::_validate_property(property);
}

Expand All @@ -222,6 +264,18 @@ void Light3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_cull_mask", "cull_mask"), &Light3D::set_cull_mask);
ClassDB::bind_method(D_METHOD("get_cull_mask"), &Light3D::get_cull_mask);

ClassDB::bind_method(D_METHOD("set_enable_distance_fade", "enable"), &Light3D::set_enable_distance_fade);
ClassDB::bind_method(D_METHOD("is_distance_fade_enabled"), &Light3D::is_distance_fade_enabled);

ClassDB::bind_method(D_METHOD("set_distance_fade_begin", "distance"), &Light3D::set_distance_fade_begin);
ClassDB::bind_method(D_METHOD("get_distance_fade_begin"), &Light3D::get_distance_fade_begin);

ClassDB::bind_method(D_METHOD("set_distance_fade_shadow", "distance"), &Light3D::set_distance_fade_shadow);
ClassDB::bind_method(D_METHOD("get_distance_fade_shadow"), &Light3D::get_distance_fade_shadow);

ClassDB::bind_method(D_METHOD("set_distance_fade_length", "distance"), &Light3D::set_distance_fade_length);
ClassDB::bind_method(D_METHOD("get_distance_fade_length"), &Light3D::get_distance_fade_length);

ClassDB::bind_method(D_METHOD("set_color", "color"), &Light3D::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &Light3D::get_color);

Expand Down Expand Up @@ -257,6 +311,11 @@ void Light3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_transmittance_bias", PROPERTY_HINT_RANGE, "-16,16,0.01"), "set_param", "get_param", PARAM_TRANSMITTANCE_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_fog_fade", PROPERTY_HINT_RANGE, "0.01,10,0.01"), "set_param", "get_param", PARAM_SHADOW_VOLUMETRIC_FOG_FADE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_blur", PROPERTY_HINT_RANGE, "0.1,8,0.01"), "set_param", "get_param", PARAM_SHADOW_BLUR);
ADD_GROUP("Distance Fade", "distance_fade_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_begin", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater"), "set_distance_fade_begin", "get_distance_fade_begin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_shadow", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater"), "set_distance_fade_shadow", "get_distance_fade_shadow");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance_fade_length", PROPERTY_HINT_RANGE, "0.0,4096.0,0.01,or_greater"), "set_distance_fade_length", "get_distance_fade_length");
ADD_GROUP("Editor", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "is_editor_only");
ADD_GROUP("", "");
Expand Down Expand Up @@ -391,6 +450,12 @@ void DirectionalLight3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NONE;
}

if (property.name == "distance_fade_enabled" || property.name == "distance_fade_begin" || property.name == "distance_fade_shadow" || property.name == "distance_fade_length") {
// Not relevant for DirectionalLight3D, as the light LOD system only pertains to point lights.
// For DirectionalLight3D, `directional_shadow_max_distance` can be used instead.
property.usage = PROPERTY_USAGE_NONE;
}

Light3D::_validate_property(property);
}

Expand Down
16 changes: 16 additions & 0 deletions scene/3d/light_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ class Light3D : public VisualInstance3D {
bool negative = false;
bool reverse_cull = false;
uint32_t cull_mask = 0;
bool distance_fade_enabled = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
real_t distance_fade_length = 10.0;
RS::LightType type = RenderingServer::LIGHT_DIRECTIONAL;
bool editor_only = false;
void _update_visibility();
Expand Down Expand Up @@ -107,6 +111,18 @@ class Light3D : public VisualInstance3D {
void set_negative(bool p_enable);
bool is_negative() const;

void set_enable_distance_fade(bool p_enable);
bool is_distance_fade_enabled() const;

void set_distance_fade_begin(real_t p_distance);
real_t get_distance_fade_begin() const;

void set_distance_fade_shadow(real_t p_distance);
real_t get_distance_fade_shadow() const;

void set_distance_fade_length(real_t p_length);
real_t get_distance_fade_length() const;

void set_cull_mask(uint32_t p_cull_mask);
uint32_t get_cull_mask() const;

Expand Down
1 change: 1 addition & 0 deletions servers/rendering/rasterizer_dummy.h
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ class RasterizerStorageDummy : public RendererStorage {
void light_set_projector(RID p_light, RID p_texture) override {}
void light_set_negative(RID p_light, bool p_enable) override {}
void light_set_cull_mask(RID p_light, uint32_t p_mask) override {}
void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override {}
void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override {}
void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override {}
void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override {}
Expand Down
63 changes: 59 additions & 4 deletions servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3440,17 +3440,45 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
continue;
}

const real_t distance = camera_plane.distance_to(li->transform.origin);

if (storage->light_is_distance_fade_enabled(li->light)) {
const float fade_begin = storage->light_get_distance_fade_begin(li->light);
const float fade_length = storage->light_get_distance_fade_length(li->light);

if (distance > fade_begin) {
if (distance > fade_begin + fade_length) {
// Out of range, don't draw this light to improve performance.
continue;
}
}
}

cluster.omni_light_sort[cluster.omni_light_count].instance = li;
cluster.omni_light_sort[cluster.omni_light_count].depth = camera_plane.distance_to(li->transform.origin);
cluster.omni_light_sort[cluster.omni_light_count].depth = distance;
cluster.omni_light_count++;
} break;
case RS::LIGHT_SPOT: {
if (cluster.spot_light_count >= cluster.max_lights) {
continue;
}

const real_t distance = camera_plane.distance_to(li->transform.origin);

if (storage->light_is_distance_fade_enabled(li->light)) {
const float fade_begin = storage->light_get_distance_fade_begin(li->light);
const float fade_length = storage->light_get_distance_fade_length(li->light);

if (distance > fade_begin) {
if (distance > fade_begin + fade_length) {
// Out of range, don't draw this light to improve performance.
continue;
}
}
}

cluster.spot_light_sort[cluster.spot_light_count].instance = li;
cluster.spot_light_sort[cluster.spot_light_count].depth = camera_plane.distance_to(li->transform.origin);
cluster.spot_light_sort[cluster.spot_light_count].depth = distance;
cluster.spot_light_count++;
} break;
}
Expand Down Expand Up @@ -3494,7 +3522,24 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const

light_data.attenuation = storage->light_get_param(base, RS::LIGHT_PARAM_ATTENUATION);

float energy = sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY) * Math_PI;
// Reuse fade begin, fade length and distance for shadow LOD determination later.
float fade_begin = 0.0;
float fade_length = 0.0;
real_t distance = 0.0;

float fade = 1.0;
if (storage->light_is_distance_fade_enabled(li->light)) {
fade_begin = storage->light_get_distance_fade_begin(li->light);
fade_length = storage->light_get_distance_fade_length(li->light);
distance = camera_plane.distance_to(li->transform.origin);

if (distance > fade_begin) {
// Use `smoothstep()` to make opacity changes more gradual and less noticeable to the player.
fade = Math::smoothstep(0.0f, 1.0f, 1.0f - float(distance - fade_begin) / fade_length);
}
}

float energy = sign * storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY) * Math_PI * fade;

light_data.color[0] = linear_col.r * energy;
light_data.color[1] = linear_col.g * energy;
Expand Down Expand Up @@ -3555,7 +3600,17 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
light_data.projector_rect[3] = 0;
}

if (shadow_atlas && shadow_atlas->shadow_owners.has(li->self)) {
const bool needs_shadow = shadow_atlas && shadow_atlas->shadow_owners.has(li->self);

bool in_shadow_range = true;
if (needs_shadow && storage->light_is_distance_fade_enabled(li->light)) {
if (distance > storage->light_get_distance_fade_shadow(li->light)) {
// Out of range, don't draw shadows to improve performance.
in_shadow_range = false;
}
}

if (needs_shadow && in_shadow_range) {
// fill in the shadow information

light_data.shadow_enabled = true;
Expand Down
10 changes: 10 additions & 0 deletions servers/rendering/renderer_rd/renderer_storage_rd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6600,6 +6600,16 @@ void RendererStorageRD::light_set_cull_mask(RID p_light, uint32_t p_mask) {
light->dependency.changed_notify(DEPENDENCY_CHANGED_LIGHT);
}

void RendererStorageRD::light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) {
Light *light = light_owner.get_or_null(p_light);
ERR_FAIL_COND(!light);

light->distance_fade = p_enabled;
light->distance_fade_begin = p_begin;
light->distance_fade_shadow = p_shadow;
light->distance_fade_length = p_length;
}

void RendererStorageRD::light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) {
Light *light = light_owner.get_or_null(p_light);
ERR_FAIL_COND(!light);
Expand Down
25 changes: 25 additions & 0 deletions servers/rendering/renderer_rd/renderer_storage_rd.h
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,10 @@ class RendererStorageRD : public RendererStorage {
RS::LightBakeMode bake_mode = RS::LIGHT_BAKE_DYNAMIC;
uint32_t max_sdfgi_cascade = 2;
uint32_t cull_mask = 0xFFFFFFFF;
bool distance_fade = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
real_t distance_fade_length = 10.0;
RS::LightOmniShadowMode omni_shadow_mode = RS::LIGHT_OMNI_SHADOW_DUAL_PARABOLOID;
RS::LightDirectionalShadowMode directional_shadow_mode = RS::LIGHT_DIRECTIONAL_SHADOW_ORTHOGONAL;
bool directional_blend_splits = false;
Expand Down Expand Up @@ -1841,6 +1845,7 @@ class RendererStorageRD : public RendererStorage {
void light_set_projector(RID p_light, RID p_texture);
void light_set_negative(RID p_light, bool p_enable);
void light_set_cull_mask(RID p_light, uint32_t p_mask);
void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length);
void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled);
void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode);
void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade);
Expand Down Expand Up @@ -1899,6 +1904,26 @@ class RendererStorageRD : public RendererStorage {
return light->cull_mask;
}

_FORCE_INLINE_ bool light_is_distance_fade_enabled(RID p_light) {
const Light *light = light_owner.get_or_null(p_light);
return light->distance_fade;
}

_FORCE_INLINE_ float light_get_distance_fade_begin(RID p_light) {
const Light *light = light_owner.get_or_null(p_light);
return light->distance_fade_begin;
}

_FORCE_INLINE_ float light_get_distance_fade_shadow(RID p_light) {
const Light *light = light_owner.get_or_null(p_light);
return light->distance_fade_shadow;
}

_FORCE_INLINE_ float light_get_distance_fade_length(RID p_light) {
const Light *light = light_owner.get_or_null(p_light);
return light->distance_fade_length;
}

_FORCE_INLINE_ bool light_has_shadow(RID p_light) const {
const Light *light = light_owner.get_or_null(p_light);
ERR_FAIL_COND_V(!light, RS::LIGHT_DIRECTIONAL);
Expand Down
1 change: 1 addition & 0 deletions servers/rendering/renderer_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ class RendererStorage {
virtual void light_set_projector(RID p_light, RID p_texture) = 0;
virtual void light_set_negative(RID p_light, bool p_enable) = 0;
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) = 0;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) = 0;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) = 0;
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) = 0;
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) = 0;
Expand Down
1 change: 1 addition & 0 deletions servers/rendering/rendering_server_default.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ class RenderingServerDefault : public RenderingServer {
FUNC2(light_set_projector, RID, RID)
FUNC2(light_set_negative, RID, bool)
FUNC2(light_set_cull_mask, RID, uint32_t)
FUNC5(light_set_distance_fade, RID, bool, float, float, float)
FUNC2(light_set_reverse_cull_face_mode, RID, bool)
FUNC2(light_set_bake_mode, RID, LightBakeMode)
FUNC2(light_set_max_sdfgi_cascade, RID, uint32_t)
Expand Down
Loading

0 comments on commit b1a295b

Please sign in to comment.