diff --git a/doc/classes/GeometryInstance.xml b/doc/classes/GeometryInstance.xml
index 02d3b327409d..8e936038b875 100644
--- a/doc/classes/GeometryInstance.xml
+++ b/doc/classes/GeometryInstance.xml
@@ -46,20 +46,18 @@
Scale factor for the generated baked lightmap. Useful for adding detail to certain mesh instances.
- The GeometryInstance's max LOD distance.
- [b]Note:[/b] This property currently has no effect.
+ The GeometryInstance's maximum level of detail (LOD) distance. The [GeometryInstance] will not be drawn if the camera is further away than [member lod_max_distance] units from the [GeometryInstance]'s origin. Use this to improve performance by hiding [GeometryInstance]s when they don't contribute much to the scene's visual output. See also [member lod_max_hysteresis].
+ [b]Note:[/b] LOD is currently not applied to shadow rendering.
- The GeometryInstance's max LOD margin.
- [b]Note:[/b] This property currently has no effect.
+ The [GeometryInstance]'s maximum level of detail (LOD) margin. Margins are symmetrical around [member lod_max_distance]. This can be set to a value greater than [code]0.0[/code] to prevent LOD levels from "flip-flopping" when the camera moves back and forth (at the cost of less effective LOD levels being chosen at a given distance). See also [member lod_max_distance].
- The GeometryInstance's min LOD distance.
- [b]Note:[/b] This property currently has no effect.
+ The GeometryInstance's minimum level of detail (LOD) distance. The [GeometryInstance] will not be drawn if the camera is closer than [member lod_max_distance] units from the [GeometryInstance]'s origin. Use this to improve performance by hiding [GeometryInstance]s when they don't contribute much to the scene's visual output.
+ [b]Note:[/b] LOD is currently not applied to shadow rendering. See also [member lod_min_hysteresis].
- The GeometryInstance's min LOD margin.
- [b]Note:[/b] This property currently has no effect.
+ The [GeometryInstance]'s minimum level of detail (LOD) margin. Margins are symmetrical around [member lod_max_distance]. This can be set to a value greater than [code]0.0[/code] to prevent LOD levels from "flip-flopping" when the camera moves back and forth (at the cost of less effective LOD levels being chosen at a given distance). See also [member lod_min_distance].
The material overlay for the whole geometry.
diff --git a/doc/classes/VisualServer.xml b/doc/classes/VisualServer.xml
index d7fa5bbe819f..fd91474f3461 100644
--- a/doc/classes/VisualServer.xml
+++ b/doc/classes/VisualServer.xml
@@ -1364,7 +1364,7 @@
- Not implemented in Godot 3.x.
+ Sets the level of detail (LOD) thresholds on the specified [code]instance[/code]. See also [member GeometryInstance.lod_min_distance], [member GeometryInstance.lod_max_distance], [member GeometryInstance.lod_min_hysteresis] and [member GeometryInstance.lod_max_hysteresis].
diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp
index 969b2fa81eaa..f51be9d9f9c0 100644
--- a/servers/visual/visual_server_scene.cpp
+++ b/servers/visual/visual_server_scene.cpp
@@ -1978,6 +1978,13 @@ void VisualServerScene::instance_geometry_set_material_overlay(RID p_instance, R
}
void VisualServerScene::instance_geometry_set_draw_range(RID p_instance, float p_min, float p_max, float p_min_margin, float p_max_margin) {
+ Instance *instance = instance_owner.get(p_instance);
+ ERR_FAIL_COND(!instance);
+
+ instance->lod_begin = p_min;
+ instance->lod_end = p_max;
+ instance->lod_begin_hysteresis = p_min_margin;
+ instance->lod_end_hysteresis = p_max_margin;
}
void VisualServerScene::instance_geometry_set_as_instance_lod(RID p_instance, RID p_as_lod_of_instance) {
}
@@ -2847,7 +2854,7 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view
Transform camera_transform = _interpolation_data.interpolation_enabled ? camera->get_transform_interpolated() : camera->transform;
- _prepare_scene(camera_transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint);
+ _prepare_scene(camera_transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint, &camera->lod_hysteresis_visible_state);
_render_scene(camera_transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1);
#endif
}
@@ -2926,17 +2933,17 @@ void VisualServerScene::render_camera(Ref &p_interface, ARVRInter
mono_transform *= apply_z_shift;
// now prepare our scene with our adjusted transform projection matrix
- _prepare_scene(mono_transform, combined_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint);
+ _prepare_scene(mono_transform, combined_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint, &camera->lod_hysteresis_visible_state);
} else if (p_eye == ARVRInterface::EYE_MONO) {
// For mono render, prepare as per usual
- _prepare_scene(cam_transform, camera_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint);
+ _prepare_scene(cam_transform, camera_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint, &camera->lod_hysteresis_visible_state);
}
// And render our scene...
_render_scene(cam_transform, camera_matrix, p_eye, false, camera->env, p_scenario, p_shadow_atlas, RID(), -1);
};
-void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int32_t &r_previous_room_id_hint) {
+void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int32_t &r_previous_room_id_hint, OAHashMap *lod_visible_state) {
// Note, in stereo rendering:
// - p_cam_transform will be a transform in the middle of our two eyes
// - p_cam_projection is a wider frustrum that encompasses both eyes
@@ -3035,6 +3042,50 @@ void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const Ca
InstanceGeometryData *geom = static_cast(ins->base_data);
+ // Calculate instance->depth from the camera.
+ const Vector3 aabb_center = ins->transformed_aabb.position + (ins->transformed_aabb.size * 0.5);
+ if (p_cam_orthogonal) {
+ ins->depth = near_plane.distance_to(aabb_center);
+ } else {
+ ins->depth = p_cam_transform.origin.distance_to(aabb_center);
+ }
+
+ // If LOD is active, and the instance is not within its LOD range, don't render it.
+ if (ins->lod_begin > CMP_EPSILON || ins->lod_end > CMP_EPSILON) { // LOD valid
+ bool prev_lod_state = false;
+ if (lod_visible_state != nullptr) {
+ lod_visible_state->lookup(ins->self.get_id(), prev_lod_state);
+ }
+
+ float lod_begin_with_hys = ins->lod_begin;
+ float lod_end_with_hys = ins->lod_end;
+ if (prev_lod_state) {
+ lod_begin_with_hys -= ins->lod_begin_hysteresis / 2.f;
+ lod_end_with_hys += ins->lod_end_hysteresis / 2.f;
+ } else {
+ lod_begin_with_hys += ins->lod_begin_hysteresis / 2.f;
+ lod_end_with_hys -= ins->lod_end_hysteresis / 2.f;
+ }
+
+ if (ins->lod_begin < CMP_EPSILON) {
+ lod_begin_with_hys = -Math_INF;
+ }
+ if (ins->lod_end < CMP_EPSILON) {
+ lod_end_with_hys = +Math_INF;
+ }
+
+ if (lod_begin_with_hys <= ins->depth && ins->depth < lod_end_with_hys) {
+ if (lod_visible_state != nullptr) {
+ lod_visible_state->set(ins->self.get_id(), true);
+ }
+ } else {
+ if (lod_visible_state != nullptr) {
+ lod_visible_state->set(ins->self.get_id(), false);
+ }
+ keep = false;
+ }
+ }
+
if (ins->redraw_if_visible) {
VisualServerRaster::redraw_request(false);
}
@@ -3349,7 +3400,8 @@ bool VisualServerScene::_render_reflection_probe_step(Instance *p_instance, int
shadow_atlas = scenario->reflection_probe_shadow_atlas;
}
- _prepare_scene(xform, cm, false, RID(), VSG::storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, reflection_probe->previous_room_id_hint);
+ // No LOD hysteresis handling for ReflectionProbes.
+ _prepare_scene(xform, cm, false, RID(), VSG::storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, reflection_probe->previous_room_id_hint, nullptr);
bool async_forbidden_backup = VSG::storage->is_shader_async_hidden_forbidden();
VSG::storage->set_shader_async_hidden_forbidden(true);
diff --git a/servers/visual/visual_server_scene.h b/servers/visual/visual_server_scene.h
index 06fc94825d2a..7d4316118a4e 100644
--- a/servers/visual/visual_server_scene.h
+++ b/servers/visual/visual_server_scene.h
@@ -36,6 +36,7 @@
#include "core/math/bvh.h"
#include "core/math/geometry.h"
#include "core/math/octree.h"
+#include "core/oa_hash_map.h"
#include "core/os/semaphore.h"
#include "core/os/thread.h"
#include "core/safe_refcount.h"
@@ -89,6 +90,7 @@ class VisualServerScene {
bool vaspect : 1;
TransformInterpolator::Method interpolation_method : 3;
+ OAHashMap lod_hysteresis_visible_state;
int32_t previous_room_id_hint;
Transform get_transform_interpolated() const;
@@ -835,7 +837,7 @@ class VisualServerScene {
_FORCE_INLINE_ bool _light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario);
- void _prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int32_t &r_previous_room_id_hint);
+ void _prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int32_t &r_previous_room_id_hint, OAHashMap *lod_visible_state);
void _render_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, const int p_eye, bool p_cam_orthogonal, RID p_force_environment, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int p_reflection_probe_pass);
void render_empty_scene(RID p_scenario, RID p_shadow_atlas);