Skip to content

Commit

Permalink
Implement LOD range in GeometryInstance
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
Calinou and Windfisch committed May 27, 2022
1 parent b541b57 commit 55c258e
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 15 deletions.
14 changes: 6 additions & 8 deletions doc/classes/GeometryInstance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,18 @@
Scale factor for the generated baked lightmap. Useful for adding detail to certain mesh instances.
</member>
<member name="lod_max_distance" type="float" setter="set_lod_max_distance" getter="get_lod_max_distance" default="0.0">
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.
</member>
<member name="lod_max_hysteresis" type="float" setter="set_lod_max_hysteresis" getter="get_lod_max_hysteresis" default="0.0">
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].
</member>
<member name="lod_min_distance" type="float" setter="set_lod_min_distance" getter="get_lod_min_distance" default="0.0">
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].
</member>
<member name="lod_min_hysteresis" type="float" setter="set_lod_min_hysteresis" getter="get_lod_min_hysteresis" default="0.0">
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].
</member>
<member name="material_overlay" type="Material" setter="set_material_overlay" getter="get_material_overlay">
The material overlay for the whole geometry.
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/VisualServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1364,7 +1364,7 @@
<argument index="3" name="min_margin" type="float" />
<argument index="4" name="max_margin" type="float" />
<description>
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].
</description>
</method>
<method name="instance_geometry_set_flag">
Expand Down
62 changes: 57 additions & 5 deletions servers/visual/visual_server_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -2926,17 +2933,17 @@ void VisualServerScene::render_camera(Ref<ARVRInterface> &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<uint32_t, bool> *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
Expand Down Expand Up @@ -3035,6 +3042,50 @@ void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const Ca

InstanceGeometryData *geom = static_cast<InstanceGeometryData *>(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);
}
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion servers/visual/visual_server_scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -89,6 +90,7 @@ class VisualServerScene {
bool vaspect : 1;
TransformInterpolator::Method interpolation_method : 3;

OAHashMap<uint32_t, bool> lod_hysteresis_visible_state;
int32_t previous_room_id_hint;

Transform get_transform_interpolated() const;
Expand Down Expand Up @@ -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<uint32_t, bool> *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);

Expand Down

0 comments on commit 55c258e

Please sign in to comment.