From 82337ab99c129e84f6f7efbd4ca80b679f837de5 Mon Sep 17 00:00:00 2001 From: SIsilicon Date: Mon, 12 Oct 2020 18:46:47 -0500 Subject: [PATCH 1/4] Implemented pooled LOD updating via LODManager. Improved Node UI. --- README.md | 27 ++----- addons/lod/lod_cpu_particles.gd | 67 ++++++++--------- addons/lod/lod_manager.gd | 48 +++++++++++++ addons/lod/lod_omni_light.gd | 80 +++++++++++---------- addons/lod/lod_particles.gd | 67 ++++++++--------- addons/lod/lod_spatial.gd | 124 ++++++++++++++++++++------------ addons/lod/lod_spot_light.gd | 80 +++++++++++---------- addons/lod/plugin.gd | 8 +++ 8 files changed, 293 insertions(+), 208 deletions(-) create mode 100644 addons/lod/lod_manager.gd diff --git a/README.md b/README.md index 842020d..cbf1a17 100644 --- a/README.md +++ b/README.md @@ -125,31 +125,12 @@ The distance bias (in 3D units) to use for LOD calculations. Positive values will improve performance at the cost of visual quality, whereas negative values will improve visual quality at the cost of performance. -### `lod/refresh_rate` +### `lod/refresh_threshold_ms` -*Default:* `0.25` +*Default:* `10` -The rate at which the LOD mesh and particle instances update (in seconds). Lower -values are more reactive but use more CPU. Each LOD instance uses a random -jitter to avoid applying updates on all instances at the same time. - -Since meshes and particles are updated in a "discrete" manner rather than a -continuus one, the default refresh rate is quite low. The difference is hardly -visible, yet it helps decrease CPU usage significantly in scenes with hundreds -of instances (or more). - -### `lod/light_refresh_rate` - -*Default:* `0.05` - -The rate at which the LOD light instances update (in seconds). Lower values are -more reactive but use more CPU. Each LOD instance uses a random jitter to avoid -applying updates on all instances at the same time. - -Since lights are updated in a "continuous" manner rather than a discrete one, -the default refresh rate is relatively high. Despite not quite being 60 FPS -(`0.01666`), it often looks very close in practice unless the camera is moving -really fast. +How much time can be used to update LOD meshes, lights and particle instances (in milliseconds). Higher +values allow more LODs to be processed at once but uses more CPU. ## Tips and tricks diff --git a/addons/lod/lod_cpu_particles.gd b/addons/lod/lod_cpu_particles.gd index e9c381e..85002b1 100644 --- a/addons/lod/lod_cpu_particles.gd +++ b/addons/lod/lod_cpu_particles.gd @@ -1,22 +1,15 @@ # Copyright © 2020 Hugo Locurcio and contributors - MIT License # See `LICENSE.md` included in the source distribution for details. +tool extends CPUParticles class_name LODCPUParticles, "lod_cpu_particles.svg" # If `false`, LOD won't update anymore. This can be used for performance comparison # purposes. -export var enable_lod := true +var enable_lod := true setget set_enable_lod # The maximum particle emitting distance in units. Past this distance, particles will no longer emit. -export(float, 0.0, 1000.0, 0.1) var max_emit_distance := 50 - -# The rate at which LODs will be updated (in seconds). Lower values are more reactive -# but use more CPU, which is especially noticeable with large amounts of LOD-enabled nodes. -# Set this accordingly depending on your camera movement speed. -# The default value should suit most projects already. -# Note: Slow cameras don't need to have LOD-enabled objects update their status often. -# This can overridden by setting the project setting `lod/refresh_rate`. -var refresh_rate := 0.25 +var max_emit_distance := 50.0 # The LOD bias in units. # Positive values will decrease the detail level and improve performance. @@ -24,36 +17,46 @@ var refresh_rate := 0.25 # This can overridden by setting the project setting `lod/bias`. var lod_bias := 0.0 -# The internal refresh timer. -var timer := 0.0 - - -func _ready() -> void: - if ProjectSettings.has_setting("lod/particle_bias"): - lod_bias = ProjectSettings.get_setting("lod/particle_bias") - if ProjectSettings.has_setting("lod/refresh_rate"): - refresh_rate = ProjectSettings.get_setting("lod/refresh_rate") - # Add random jitter to the timer to ensure LODs don't all swap at the same time. - randomize() - timer += rand_range(0, refresh_rate) +func set_enable_lod(value: bool) -> void: + enable_lod = value + if is_inside_tree() and Engine.editor_hint: + if enable_lod: + LODManager.register_lod_object(self) + else: + LODManager.unregister_lod_object(self) -# Despite LOD not being related to physics, we chose to run in `_physics_process()` -# to minimize the amount of method calls per second (and therefore decrease CPU usage). -func _physics_process(delta: float) -> void: - if not enable_lod: - return +func update_lod() -> void: # We need a camera to do the rest. var camera := get_viewport().get_camera() if camera == null: return - if timer <= refresh_rate: - timer += delta + var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias + emitting = distance < max_emit_distance + + +func _get_property_list() -> Array: + var properties := [ + {name="LODCPUParticles", type=TYPE_NIL, usage=PROPERTY_USAGE_CATEGORY}, + {name="enable_lod", type=TYPE_BOOL}, + {name="max_emit_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + ] + return properties + + +func _ready() -> void: + if Engine.editor_hint: return + if ProjectSettings.has_setting("lod/particle_bias"): + lod_bias = ProjectSettings.get_setting("lod/particle_bias") - timer = 0.0 + LODManager.register_lod_object(self) + update_lod() - var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias - emitting = distance < max_emit_distance + +func _exit_tree() -> void: + if Engine.editor_hint: + return + LODManager.unregister_lod_object(self) diff --git a/addons/lod/lod_manager.gd b/addons/lod/lod_manager.gd new file mode 100644 index 0000000..ab3e0ed --- /dev/null +++ b/addons/lod/lod_manager.gd @@ -0,0 +1,48 @@ +# Copyright © 2020 Hugo Locurcio and contributors - MIT License +# See `LICENSE.md` included in the source distribution for details. +extends Node + +# Registered lod objects. +var lod_objects := [] + +# Index in the lod array that was last processed. +var current_idx := 0 + +# Number of lods processed in the current frame. Access this for debugging. +var lods_processed := 0 + +# How much time can be used to process lods in milliseconds. +# If not all the lods can be processed within this time, they'll be processed in the next frame. +var refresh_threshold_ms := 10 + + +func _ready() -> void: + if ProjectSettings.has_setting("lod/refresh_threshold_ms"): + refresh_threshold_ms = ProjectSettings.get_setting("lod/refresh_threshold_ms") + + +func _process(_delta: float) -> void: + if current_idx >= lod_objects.size(): + current_idx = 0 + + lods_processed = 0 + var prev_idx := current_idx + var time := OS.get_ticks_msec() + + while OS.get_ticks_msec() - time < refresh_threshold_ms: + lod_objects[current_idx].update_lod() + lods_processed += 1 + + current_idx = wrapi(current_idx + 1, 0, lod_objects.size()) + if prev_idx == current_idx: + break + +# Register lod object, if it's not already in the list. +func register_lod_object(object: Spatial) -> void: + if not lod_objects.has(object): + lod_objects.append(object) + +# Unregister lod object, if it's still in the list. +func unregister_lod_object(object: Spatial) -> void: + if lod_objects.has(object): + lod_objects.erase(object) diff --git a/addons/lod/lod_omni_light.gd b/addons/lod/lod_omni_light.gd index 1db23ee..fbf3100 100644 --- a/addons/lod/lod_omni_light.gd +++ b/addons/lod/lod_omni_light.gd @@ -1,35 +1,26 @@ # Copyright © 2020 Hugo Locurcio and contributors - MIT License # See `LICENSE.md` included in the source distribution for details. +tool extends OmniLight class_name LODOmniLight, "lod_omni_light.svg" # If `false`, LOD won't update anymore. This can be used for performance comparison # purposes. -export var enable_lod := true +var enable_lod := true # The maximum shadow distance in units. Past this distance, the shadow will be disabled. -export(float, 0.0, 1000.0, 0.1) var shadow_max_distance := 25 +var shadow_max_distance := 25.0 # The distance factor at which the shadow starts fading. # A value of 0.0 will result in the smoothest transition whereas a value of 1.0 disables fading. -export(float, 0.0, 1.0, 0.1) var shadow_fade_start := 0.8 +var shadow_fade_start := 0.8 # The maximum shadow distance in units. Past this distance, the light will be hidden. -export(float, 0.0, 1000.0, 0.1) var light_max_distance := 50 +var light_max_distance := 50.0 # The distance factor at which the light starts fading. # A value of 0.0 will result in the smoothest transition whereas a value of 1.0 disables fading. -export(float, 0.0, 1.0, 0.1) var light_fade_start := 0.8 - -# The rate at which LODs will be updated (in seconds). Lower values are more reactive -# but use more CPU, which is especially noticeable with large amounts of LOD-enabled nodes. -# Set this accordingly depending on your camera movement speed. -# The default value should suit most projects already. -# Note: Slow cameras don't need to have LOD-enabled objects update their status often. -# By default, lights have their LOD updated faster than other LOD nodes since their -# light/shadow intensity needs to change as smoothly as posible. -# This can overridden by setting the project setting `lod/light_refresh_rate`. -var refresh_rate := 0.05 +var light_fade_start := 0.8 # The LOD bias in units. # Positive values will decrease the detail level and improve performance. @@ -37,40 +28,25 @@ var refresh_rate := 0.05 # This can overridden by setting the project setting `lod/bias`. var lod_bias := 0.0 -# The internal refresh timer. -var timer := 0.0 - # The light's energy when it was instanced. var base_light_energy := light_energy -func _ready() -> void: - if ProjectSettings.has_setting("lod/light_bias"): - lod_bias = ProjectSettings.get_setting("lod/light_bias") - if ProjectSettings.has_setting("lod/light_refresh_rate"): - refresh_rate = ProjectSettings.get_setting("lod/light_refresh_rate") - - # Add random jitter to the timer to ensure LODs don't all swap at the same time. - randomize() - timer += rand_range(0, refresh_rate) +func set_enable_lod(value: bool) -> void: + enable_lod = value + if is_inside_tree() and Engine.editor_hint: + if enable_lod: + LODManager.register_lod_object(self) + else: + LODManager.unregister_lod_object(self) -# Despite LOD not being related to physics, we chose to run in `_physics_process()` -# to minimize the amount of method calls per second (and therefore decrease CPU usage). -func _physics_process(delta: float) -> void: - if not enable_lod: - return +func update_lod() -> void: # We need a camera to do the rest. var camera := get_viewport().get_camera() if camera == null: return - if timer <= refresh_rate: - timer += delta - return - - timer = 0.0 - var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias visible = distance < light_max_distance @@ -90,3 +66,31 @@ func _physics_process(delta: float) -> void: # We're close enough to the light to show its shadow at full darkness. shadow_value = 0.0 shadow_color = Color(shadow_value, shadow_value, shadow_value) + + +func _get_property_list() -> Array: + var properties := [ + {name="LODOmniLight", type=TYPE_NIL, usage=PROPERTY_USAGE_CATEGORY}, + {name="enable_lod", type=TYPE_BOOL}, + {name="shadow_max_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + {name="shadow_fade_start", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="0,1,0.01"}, + {name="light_max_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + {name="light_fade_start", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="0,1,0.01"}, + ] + return properties + + +func _ready() -> void: + if Engine.editor_hint: + return + if ProjectSettings.has_setting("lod/light_bias"): + lod_bias = ProjectSettings.get_setting("lod/light_bias") + + LODManager.register_lod_object(self) + update_lod() + + +func _exit_tree() -> void: + if Engine.editor_hint: + return + LODManager.unregister_lod_object(self) diff --git a/addons/lod/lod_particles.gd b/addons/lod/lod_particles.gd index 4b9f77c..dae1b51 100644 --- a/addons/lod/lod_particles.gd +++ b/addons/lod/lod_particles.gd @@ -1,22 +1,15 @@ # Copyright © 2020 Hugo Locurcio and contributors - MIT License # See `LICENSE.md` included in the source distribution for details. +tool extends Particles class_name LODParticles, "lod_particles.svg" # If `false`, LOD won't update anymore. This can be used for performance comparison # purposes. -export var enable_lod := true +var enable_lod := true setget set_enable_lod # The maximum particle emitting distance in units. Past this distance, particles will no longer emit. -export(float, 0.0, 1000.0, 0.1) var max_emit_distance := 50 - -# The rate at which LODs will be updated (in seconds). Lower values are more reactive -# but use more CPU, which is especially noticeable with large amounts of LOD-enabled nodes. -# Set this accordingly depending on your camera movement speed. -# The default value should suit most projects already. -# Note: Slow cameras don't need to have LOD-enabled objects update their status often. -# This can overridden by setting the project setting `lod/refresh_rate`. -var refresh_rate := 0.25 +var max_emit_distance := 50.0 # The LOD bias in units. # Positive values will decrease the detail level and improve performance. @@ -24,36 +17,46 @@ var refresh_rate := 0.25 # This can overridden by setting the project setting `lod/bias`. var lod_bias := 0.0 -# The internal refresh timer. -var timer := 0.0 - - -func _ready() -> void: - if ProjectSettings.has_setting("lod/particle_bias"): - lod_bias = ProjectSettings.get_setting("lod/particle_bias") - if ProjectSettings.has_setting("lod/refresh_rate"): - refresh_rate = ProjectSettings.get_setting("lod/refresh_rate") - # Add random jitter to the timer to ensure LODs don't all swap at the same time. - randomize() - timer += rand_range(0, refresh_rate) +func set_enable_lod(value: bool) -> void: + enable_lod = value + if is_inside_tree() and Engine.editor_hint: + if enable_lod: + LODManager.register_lod_object(self) + else: + LODManager.unregister_lod_object(self) -# Despite LOD not being related to physics, we chose to run in `_physics_process()` -# to minimize the amount of method calls per second (and therefore decrease CPU usage). -func _physics_process(delta: float) -> void: - if not enable_lod: - return +func update_lod() -> void: # We need a camera to do the rest. var camera := get_viewport().get_camera() if camera == null: return - if timer <= refresh_rate: - timer += delta + var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias + emitting = distance < max_emit_distance + + +func _get_property_list() -> Array: + var properties := [ + {name="LODParticles", type=TYPE_NIL, usage=PROPERTY_USAGE_CATEGORY}, + {name="enable_lod", type=TYPE_BOOL}, + {name="max_emit_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + ] + return properties + + +func _ready() -> void: + if Engine.editor_hint: return + if ProjectSettings.has_setting("lod/particle_bias"): + lod_bias = ProjectSettings.get_setting("lod/particle_bias") - timer = 0.0 + LODManager.register_lod_object(self) + update_lod() - var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias - emitting = distance < max_emit_distance + +func _exit_tree() -> void: + if Engine.editor_hint: + return + LODManager.unregister_lod_object(self) diff --git a/addons/lod/lod_spatial.gd b/addons/lod/lod_spatial.gd index 39e811c..4ce1c91 100644 --- a/addons/lod/lod_spatial.gd +++ b/addons/lod/lod_spatial.gd @@ -1,29 +1,22 @@ # Copyright © 2020 Hugo Locurcio and contributors - MIT License # See `LICENSE.md` included in the source distribution for details. +tool extends Spatial class_name LODSpatial, "lod_spatial.svg" # If `false`, LOD won't update anymore. This can be used for performance comparison # purposes. -export var enable_lod := true +var enable_lod := true setget set_enable_lod # The maximum LOD 0 (high quality) distance in units. -export(float, 0.0, 1000.0, 0.1) var lod_0_max_distance := 10 +var lod_0_max_distance := 10.0 # The maximum LOD 1 (medium quality) distance in units. -export(float, 0.0, 1000.0, 0.1) var lod_1_max_distance := 25 +var lod_1_max_distance := 25.0 # The maximum LOD 2 (low quality) distance in units. # Past this distance, all LOD variants are hidden. -export(float, 0.0, 1000.0, 0.1) var lod_2_max_distance := 100 - -# The rate at which LODs will be updated (in seconds). Lower values are more reactive -# but use more CPU, which is especially noticeable with large amounts of LOD-enabled nodes. -# Set this accordingly depending on your camera movement speed. -# The default value should suit most projects already. -# Note: Slow cameras don't need to have LOD-enabled objects update their status often. -# This can overridden by setting the project setting `lod/refresh_rate`. -var refresh_rate := 0.25 +var lod_2_max_distance := 100.0 # The LOD bias in units. # Positive values will decrease the detail level and improve performance. @@ -31,41 +24,26 @@ var refresh_rate := 0.25 # This can overridden by setting the project setting `lod/bias`. var lod_bias := 0.0 -# The internal refresh timer. -var timer := 0.0 +func set_enable_lod(value: bool) -> void: + enable_lod = value + if is_inside_tree() and not Engine.editor_hint: + if enable_lod: + LODManager.register_lod_object(self) + else: + LODManager.unregister_lod_object(self) -func _ready() -> void: - if ProjectSettings.has_setting("lod/spatial_bias"): - lod_bias = ProjectSettings.get_setting("lod/spatial_bias") - if ProjectSettings.has_setting("lod/refresh_rate"): - refresh_rate = ProjectSettings.get_setting("lod/refresh_rate") - - # Add random jitter to the timer to ensure LODs don't all swap at the same time. - randomize() - timer += rand_range(0, refresh_rate) - - -# Despite LOD not being related to physics, we chose to run in `_physics_process()` -# to minimize the amount of method calls per second (and therefore decrease CPU usage). -func _physics_process(delta: float) -> void: - if not enable_lod: - return +func update_lod() -> void: # We need a camera to do the rest. var camera := get_viewport().get_camera() if camera == null: return - if timer <= refresh_rate: - timer += delta - return - - timer = 0.0 - - var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias # The LOD level to choose (lower is more detailed). var lod: int + + var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias if distance < lod_0_max_distance: lod = 0 elif distance < lod_1_max_distance: @@ -77,11 +55,67 @@ func _physics_process(delta: float) -> void: lod = 3 for node in get_children(): - # `-lod` also matches `-lod0`, `-lod1`, `-lod2`, … - if node.has_method("set_visible"): - if "-lod0" in node.name: - node.visible = lod == 0 - if "-lod1" in node.name: - node.visible = lod == 1 - if "-lod2" in node.name: - node.visible = lod == 2 + if "-lod0" in node.name: + node.visible = lod == 0 + elif "-lod1" in node.name: + node.visible = lod == 1 + elif "-lod2" in node.name: + node.visible = lod == 2 + + +func _get_property_list() -> Array: + var properties := [ + {name="LODSpatial", type=TYPE_NIL, usage=PROPERTY_USAGE_CATEGORY}, + {name="enable_lod", type=TYPE_BOOL}, + {name="lod_0_max_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + {name="lod_1_max_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + {name="lod_2_max_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + ] + return properties + + +func _ready() -> void: + if Engine.editor_hint: + return + if ProjectSettings.has_setting("lod/spatial_bias"): + lod_bias = ProjectSettings.get_setting("lod/spatial_bias") + + LODManager.register_lod_object(self) + update_lod() + + +func _exit_tree() -> void: + if Engine.editor_hint: + return + LODManager.unregister_lod_object(self) + + +## Note from SIsilicon: +## Some leftover code that's meant to calculate LODs based on screen coverage. +## Just leaving this here incase it's ever needed. +#var base_aabb: AABB +# for node in get_children(): +# if node is VisualInstance and "-lod0" in node.name: +# if base_aabb: +# base_aabb.merge(node.get_transformed_aabb()) +# else: +# base_aabb = node.get_transformed_aabb() +# var rect: Rect2 +# for i in 8: +# var screen_point := camera.unproject_position(base_aabb.get_endpoint(i)) +# if rect: +# rect = rect.expand(screen_point) +# else: +# rect = Rect2(screen_point, Vector2.ZERO) +# +# var coverage := sqrt(rect.get_area() / Rect2(Vector2.ZERO, get_viewport().size).get_area()) * 100.0 +# +# if coverage > lod_0_min_coverage: +# lod = 0 +# elif coverage > lod_1_min_coverage: +# lod = 1 +# elif coverage > lod_2_min_coverage: +# lod = 2 +# else: +# # Hide the LOD object entirely. +# lod = 3 diff --git a/addons/lod/lod_spot_light.gd b/addons/lod/lod_spot_light.gd index 63300ee..19365f7 100644 --- a/addons/lod/lod_spot_light.gd +++ b/addons/lod/lod_spot_light.gd @@ -1,35 +1,26 @@ # Copyright © 2020 Hugo Locurcio and contributors - MIT License # See `LICENSE.md` included in the source distribution for details. +tool extends SpotLight class_name LODSpotLight, "lod_spot_light.svg" # If `false`, LOD won't update anymore. This can be used for performance comparison # purposes. -export var enable_lod := true +var enable_lod := true # The maximum shadow distance in units. Past this distance, the shadow will be disabled. -export(float, 0.0, 1000.0, 0.1) var shadow_max_distance := 25 +var shadow_max_distance := 25.0 # The distance factor at which the shadow starts fading. # A value of 0.0 will result in the smoothest transition whereas a value of 1.0 disables fading. -export(float, 0.0, 1.0, 0.1) var shadow_fade_start := 0.8 +var shadow_fade_start := 0.8 # The maximum shadow distance in units. Past this distance, the light will be hidden. -export(float, 0.0, 1000.0, 0.1) var light_max_distance := 50 +var light_max_distance := 50.0 # The distance factor at which the light starts fading. # A value of 0.0 will result in the smoothest transition whereas a value of 1.0 disables fading. -export(float, 0.0, 1.0, 0.1) var light_fade_start := 0.8 - -# The rate at which LODs will be updated (in seconds). Lower values are more reactive -# but use more CPU, which is especially noticeable with large amounts of LOD-enabled nodes. -# Set this accordingly depending on your camera movement speed. -# The default value should suit most projects already. -# Note: Slow cameras don't need to have LOD-enabled objects update their status often. -# By default, lights have their LOD updated faster than other LOD nodes since their -# light/shadow intensity needs to change as smoothly as posible. -# This can overridden by setting the project setting `lod/light_refresh_rate`. -var refresh_rate := 0.05 +var light_fade_start := 0.8 # The LOD bias in units. # Positive values will decrease the detail level and improve performance. @@ -37,40 +28,25 @@ var refresh_rate := 0.05 # This can overridden by setting the project setting `lod/bias`. var lod_bias := 0.0 -# The internal refresh timer. -var timer := 0.0 - # The light's energy when it was instanced. var base_light_energy := light_energy -func _ready() -> void: - if ProjectSettings.has_setting("lod/light_bias"): - lod_bias = ProjectSettings.get_setting("lod/light_bias") - if ProjectSettings.has_setting("lod/light_refresh_rate"): - refresh_rate = ProjectSettings.get_setting("lod/light_refresh_rate") - - # Add random jitter to the timer to ensure LODs don't all swap at the same time. - randomize() - timer += rand_range(0, refresh_rate) +func set_enable_lod(value: bool) -> void: + enable_lod = value + if is_inside_tree() and Engine.editor_hint: + if enable_lod: + LODManager.register_lod_object(self) + else: + LODManager.unregister_lod_object(self) -# Despite LOD not being related to physics, we chose to run in `_physics_process()` -# to minimize the amount of method calls per second (and therefore decrease CPU usage). -func _physics_process(delta: float) -> void: - if not enable_lod: - return +func update_lod() -> void: # We need a camera to do the rest. var camera := get_viewport().get_camera() if camera == null: return - if timer <= refresh_rate: - timer += delta - return - - timer = 0.0 - var distance := camera.global_transform.origin.distance_to(global_transform.origin) + lod_bias visible = distance < light_max_distance @@ -90,3 +66,31 @@ func _physics_process(delta: float) -> void: # We're close enough to the light to show its shadow at full darkness. shadow_value = 0.0 shadow_color = Color(shadow_value, shadow_value, shadow_value) + + +func _get_property_list() -> Array: + var properties := [ + {name="LODSpotLight", type=TYPE_NIL, usage=PROPERTY_USAGE_CATEGORY}, + {name="enable_lod", type=TYPE_BOOL}, + {name="shadow_max_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + {name="shadow_fade_start", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="0,1,0.01"}, + {name="light_max_distance", type=TYPE_REAL, hint=PROPERTY_HINT_EXP_RANGE, hint_string="0,1000,0.01,or_greater"}, + {name="light_fade_start", type=TYPE_REAL, hint=PROPERTY_HINT_RANGE, hint_string="0,1,0.01"}, + ] + return properties + + +func _ready() -> void: + if Engine.editor_hint: + return + if ProjectSettings.has_setting("lod/light_bias"): + lod_bias = ProjectSettings.get_setting("lod/light_bias") + + LODManager.register_lod_object(self) + update_lod() + + +func _exit_tree() -> void: + if Engine.editor_hint: + return + LODManager.unregister_lod_object(self) diff --git a/addons/lod/plugin.gd b/addons/lod/plugin.gd index a824c97..d36abab 100644 --- a/addons/lod/plugin.gd +++ b/addons/lod/plugin.gd @@ -5,3 +5,11 @@ # once the plugin is enabled. tool extends EditorPlugin + + +func _enter_tree() -> void: + add_autoload_singleton("LODManager", "res://addons/lod/lod_manager.gd") + + +func _exit_tree() -> void: + remove_autoload_singleton("LODManager") From 9e5c76f101276870e9ae7493cecca876abae8356 Mon Sep 17 00:00:00 2001 From: SIsilicon Date: Mon, 12 Oct 2020 19:02:46 -0500 Subject: [PATCH 2/4] Fixed singleton parsing dependency. The singleton had to be loaded on runtime rather than with its own name. --- addons/lod/lod_cpu_particles.gd | 8 ++++---- addons/lod/lod_omni_light.gd | 8 ++++---- addons/lod/lod_particles.gd | 8 ++++---- addons/lod/lod_spatial.gd | 8 ++++---- addons/lod/lod_spot_light.gd | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/addons/lod/lod_cpu_particles.gd b/addons/lod/lod_cpu_particles.gd index 85002b1..f97b3c5 100644 --- a/addons/lod/lod_cpu_particles.gd +++ b/addons/lod/lod_cpu_particles.gd @@ -22,9 +22,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) else: - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -52,11 +52,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/particle_bias"): lod_bias = ProjectSettings.get_setting("lod/particle_bias") - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) diff --git a/addons/lod/lod_omni_light.gd b/addons/lod/lod_omni_light.gd index fbf3100..e753ac6 100644 --- a/addons/lod/lod_omni_light.gd +++ b/addons/lod/lod_omni_light.gd @@ -36,9 +36,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) else: - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -86,11 +86,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/light_bias"): lod_bias = ProjectSettings.get_setting("lod/light_bias") - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) diff --git a/addons/lod/lod_particles.gd b/addons/lod/lod_particles.gd index dae1b51..11df112 100644 --- a/addons/lod/lod_particles.gd +++ b/addons/lod/lod_particles.gd @@ -22,9 +22,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) else: - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -52,11 +52,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/particle_bias"): lod_bias = ProjectSettings.get_setting("lod/particle_bias") - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) diff --git a/addons/lod/lod_spatial.gd b/addons/lod/lod_spatial.gd index 4ce1c91..b63fcb9 100644 --- a/addons/lod/lod_spatial.gd +++ b/addons/lod/lod_spatial.gd @@ -29,9 +29,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and not Engine.editor_hint: if enable_lod: - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) else: - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -80,14 +80,14 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/spatial_bias"): lod_bias = ProjectSettings.get_setting("lod/spatial_bias") - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) ## Note from SIsilicon: diff --git a/addons/lod/lod_spot_light.gd b/addons/lod/lod_spot_light.gd index 19365f7..5ad4def 100644 --- a/addons/lod/lod_spot_light.gd +++ b/addons/lod/lod_spot_light.gd @@ -36,9 +36,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) else: - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -86,11 +86,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/light_bias"): lod_bias = ProjectSettings.get_setting("lod/light_bias") - LODManager.register_lod_object(self) + Engine.get_singleton("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - LODManager.unregister_lod_object(self) + Engine.get_singleton("LODManager").unregister_lod_object(self) From 2c679d29c386bd2695af231c21566a8c244c162c Mon Sep 17 00:00:00 2001 From: SIsilicon Date: Tue, 13 Oct 2020 09:52:33 -0500 Subject: [PATCH 3/4] Reduced default refresh threshold. Updated Changelog. --- CHANGELOG.md | 7 +++++++ README.md | 2 +- addons/lod/lod_manager.gd | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed24ab..c245567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +* All LOD objects are now updated within a user specified time. +### Changed +* Node Inspector UIs now look more like the ones for built-in nodes, rather than merely scripts. +### Removed +* Refresh times for the LOD nodes. + ## 1.0.0 - 2020-09-24 - Initial versioned release. diff --git a/README.md b/README.md index cbf1a17..2ec800e 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ will improve visual quality at the cost of performance. ### `lod/refresh_threshold_ms` -*Default:* `10` +*Default:* `5` How much time can be used to update LOD meshes, lights and particle instances (in milliseconds). Higher values allow more LODs to be processed at once but uses more CPU. diff --git a/addons/lod/lod_manager.gd b/addons/lod/lod_manager.gd index ab3e0ed..fc35072 100644 --- a/addons/lod/lod_manager.gd +++ b/addons/lod/lod_manager.gd @@ -13,7 +13,7 @@ var lods_processed := 0 # How much time can be used to process lods in milliseconds. # If not all the lods can be processed within this time, they'll be processed in the next frame. -var refresh_threshold_ms := 10 +var refresh_threshold_ms := 5 func _ready() -> void: From dae8c60ceaedc401d8d8c8fdc7d8370dcccef228 Mon Sep 17 00:00:00 2001 From: SIsilicon Date: Tue, 13 Oct 2020 13:31:55 -0500 Subject: [PATCH 4/4] Fixed Singleton loading AGAIN. Also fixed an error that occurs when there are no LODs to process. --- addons/lod/lod_cpu_particles.gd | 8 ++++---- addons/lod/lod_manager.gd | 3 +++ addons/lod/lod_omni_light.gd | 8 ++++---- addons/lod/lod_particles.gd | 8 ++++---- addons/lod/lod_spatial.gd | 8 ++++---- addons/lod/lod_spot_light.gd | 8 ++++---- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/addons/lod/lod_cpu_particles.gd b/addons/lod/lod_cpu_particles.gd index f97b3c5..880008f 100644 --- a/addons/lod/lod_cpu_particles.gd +++ b/addons/lod/lod_cpu_particles.gd @@ -22,9 +22,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) else: - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -52,11 +52,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/particle_bias"): lod_bias = ProjectSettings.get_setting("lod/particle_bias") - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) diff --git a/addons/lod/lod_manager.gd b/addons/lod/lod_manager.gd index ab3e0ed..e36862e 100644 --- a/addons/lod/lod_manager.gd +++ b/addons/lod/lod_manager.gd @@ -22,6 +22,9 @@ func _ready() -> void: func _process(_delta: float) -> void: + if lod_objects.empty(): + return + if current_idx >= lod_objects.size(): current_idx = 0 diff --git a/addons/lod/lod_omni_light.gd b/addons/lod/lod_omni_light.gd index e753ac6..0eb50af 100644 --- a/addons/lod/lod_omni_light.gd +++ b/addons/lod/lod_omni_light.gd @@ -36,9 +36,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) else: - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -86,11 +86,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/light_bias"): lod_bias = ProjectSettings.get_setting("lod/light_bias") - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) diff --git a/addons/lod/lod_particles.gd b/addons/lod/lod_particles.gd index 11df112..d4020a0 100644 --- a/addons/lod/lod_particles.gd +++ b/addons/lod/lod_particles.gd @@ -22,9 +22,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) else: - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -52,11 +52,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/particle_bias"): lod_bias = ProjectSettings.get_setting("lod/particle_bias") - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) diff --git a/addons/lod/lod_spatial.gd b/addons/lod/lod_spatial.gd index b63fcb9..726d193 100644 --- a/addons/lod/lod_spatial.gd +++ b/addons/lod/lod_spatial.gd @@ -29,9 +29,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and not Engine.editor_hint: if enable_lod: - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) else: - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -80,14 +80,14 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/spatial_bias"): lod_bias = ProjectSettings.get_setting("lod/spatial_bias") - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) ## Note from SIsilicon: diff --git a/addons/lod/lod_spot_light.gd b/addons/lod/lod_spot_light.gd index 5ad4def..b828608 100644 --- a/addons/lod/lod_spot_light.gd +++ b/addons/lod/lod_spot_light.gd @@ -36,9 +36,9 @@ func set_enable_lod(value: bool) -> void: enable_lod = value if is_inside_tree() and Engine.editor_hint: if enable_lod: - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) else: - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self) func update_lod() -> void: @@ -86,11 +86,11 @@ func _ready() -> void: if ProjectSettings.has_setting("lod/light_bias"): lod_bias = ProjectSettings.get_setting("lod/light_bias") - Engine.get_singleton("LODManager").register_lod_object(self) + get_tree().root.get_node("LODManager").register_lod_object(self) update_lod() func _exit_tree() -> void: if Engine.editor_hint: return - Engine.get_singleton("LODManager").unregister_lod_object(self) + get_tree().root.get_node("LODManager").unregister_lod_object(self)