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