From 55c258ee16314db6fce7c8805df122a138314552 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Wed, 13 Oct 2021 20:45:40 +0200 Subject: [PATCH] Implement LOD range in GeometryInstance Similar to the one present in `master`, this provides a LOD system for GeometryInstance-derived nodes such as MeshInstance and MultiMeshInstance. This LOD system currently doesn't obey shadow rendering, but it can still provide substantial performance improvements when used well. Coupled with the occlusion culling systems (portals/rooms or occluder spheres), this makes possible to achieve much greater performance in large detailed scenes. Co-authored-by: Florian Jung --- doc/classes/GeometryInstance.xml | 14 +++--- doc/classes/VisualServer.xml | 2 +- servers/visual/visual_server_scene.cpp | 62 +++++++++++++++++++++++--- servers/visual/visual_server_scene.h | 4 +- 4 files changed, 67 insertions(+), 15 deletions(-) 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);