Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Discrete Level of Detail #85437

Merged
merged 1 commit into from
Mar 8, 2024
Merged

Conversation

lawnjelly
Copy link
Member

@lawnjelly lawnjelly commented Nov 27, 2023

Add scene side discrete level of detail.

New LOD node for UI. LODManager added within World for automatically updating child visibilities based on distance from cameras.

Supersedes #53778
Fixes #40784

Demo Projects

Examine in the editor while moving viewer. Run, and use cursors to move camera.
testlod2.zip

Run, and use cursors to move. Contains multiple LOD nodes.
testlod3.zip

2024-02-10.08-39-55.mp4

Explanation

After exploring some fixes to #53778 I decided to try a scene side LOD system (like @Calinou 's addon).

LOD Node showing 3 example MeshInstances to be used as LODs
lod_node

LOD Node properties
hysteresis

Spatial properties
The old unused LOD properties are removed from GeometryInstance. The lod_range determines how much of the distance range is used by this LOD. So first child with 2 range will cover distance 0-2, second child with 5 range will cover distance 2-7, etc.
Children of these Spatials will also automatically be shown / hidden as a consequence of the scene tree, as the LODs are automatically selected.

log_range

Advantages & Disadvantages

There are advantages and disadvantages to using either scene side or server side approach:

Server side

Advantages

  • Can change LOD per camera

Disadvantages

  • Changing LOD per camera doesn't play nice with shadows, especially OMNI and SPOTs, so you either have to deal with self shadowing artefacts or do user side workarounds.
  • Only deals with displaying the final mesh, cannot throttle down animations, sound, script
  • Hidden LODs still do significant processing (BVH, culling, pairing etc)
  • All LOD calcs are done every frame.
  • Calculations based on AABB centre may miscalculate overlap between different LODs, resulting in either flickering gaps or overlap (and consequently more processing used, when the objective is to increase performance!).
  • Has to use new visibility_parent system in VisualServer to work around inability to use the hierarchy.

Scene side

Advantages

  • Can calculate a "best compromise" LOD based on the closest camera
  • Shadows can use the LOD chosen and thus no self shadowing artefacts, and shadows also scale
  • Can also throttle down animations, sound, scripts etc.
  • Can totally turn off display at distance from the camera.
  • Is very processing friendly, hidden LODs are removed from BVH and do no processing server side.
  • Processing is done in round robin fashion with queues, so LOD processing is limited per frame.
  • Can use the hierarchy to determine visibility - any child that is an ancestor of the LOD children will in turn be shown or hidden as per the normal scene tree behaviour, no special changes are required. Additionally, visibility change notifications can be used as normal to drive behaviour.

Disadvantages

  • LOD chosen does not vary per camera

Notes

  • There seems to be significant demand for this in 3.x based on recent twitter threads (https://twitter.com/TheConceptBoy/status/1727003872202539151 )
  • This is fairly simple but should be very flexible, would welcome further input.
  • Removes the existing stubs in VisualServer and GeometryInstance as these make no sense with this PR.
  • I may have a go at progressive LOD for 3.7 but this should do for a lot of basic use cases, and covered HLOD where multiple models are replaced by a single model at distance.
  • The generating of the threshold distances is pretty inefficient here, but is enough to get started and can be made a lot more efficient with [3.x] Deprecate NOTIFICATION_MOVED_IN_PARENT #82248 which adds children_changed signal (so the calculation can be pushed on demand instead of pulled every time).
  • After writing support for Ortho cameras, I decided for now to simply disable calculations for ortho cameras until we have a use case. The use case does not seem convincing, and these are likely to cause edge case bugs as the distance is from the plane and not the camera.
  • Cameras can turn on and off affects_lod property. Notably this is used for editor preview windows to prevent hidden windows affecting LOD.
  • I removed the lod_enabled property in GeometryInstance as I realised it wasn't necessary. If users want to have nodes unaffected by LOD, they can either parent the LOD node to a Spatial and hang the nodes from this, or use Spatial nodes as direct children of LOD (as these are not GeometryInstances they and their children will be unaffected by the LOD node).
  • There are 5 priority settings available in the LOD node properties to choose the update rate.
  • The first LOD is always chosen during saving from the editor, to prevent unnecessary diffs in files.
  • Updating LODs can be turned on and off in the editor via the view menu in the 3d view.

Discussion

This only took me today to do as an exploratory look. Discrete LOD can be done using an addon, but there seems some interest in a core solution so I put this up as an option.

Note that this also won't deal sensibly with MultiMeshes (as these are dealt with as a block rather than performing LOD on each instance within the MultiMesh). If there is demand we could maybe add functionality to manage multimeshes, perhaps for vegetation, where otherwise drawcalls would be a bottleneck. These would probably have to be dealt with quite differently internally, although the user interface could probably be similar / shared.

scene/3d/camera.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@Mickeon Mickeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to see the viability of this for 3.x. There's no direct equivalent in 4.0, so it's difficult to judge.

(I wholeheartedly appreciate removing the confusing properties that never had any effect, myself)

doc/classes/GeometryInstance.xml Outdated Show resolved Hide resolved
doc/classes/LOD.xml Outdated Show resolved Hide resolved
doc/classes/LOD.xml Outdated Show resolved Hide resolved
doc/classes/Camera.xml Outdated Show resolved Hide resolved
@lawnjelly lawnjelly force-pushed the lod_scene_side branch 3 times, most recently from fba7a66 to 4f9b903 Compare January 28, 2024 11:01
@AThousandShips
Copy link
Member

Even though they don't do anything I'd say removing these properties is breaking compatibility, they still set values so nominally someone could be using them for their own implementation, right?

@Mickeon
Copy link
Contributor

Mickeon commented Jan 28, 2024

To my dismay, yes. And they're not duds, either. They do set something in the VisualServer (even if it doesn't actually do anything). May be worth seeing some way to keep them out of view while still being functional

@lawnjelly
Copy link
Member Author

Even though they don't do anything I'd say removing these properties is breaking compatibility, they still set values so nominally someone could be using them for their own implementation, right?

See #57196 . We planned to remove these properties, but it was only delayed due to #53778 , which is superseded by this PR.

@Mickeon
Copy link
Contributor

Mickeon commented Jan 28, 2024

It's been so long that the likelihood of these properties being used by someone out there is... still low, but not impossible. The documentation was never even changed to state that they may be removed in the future. The PR is still compatibility-breaking, but... pretty small all things considered, and more welcoming than not I would say.

@lawnjelly
Copy link
Member Author

It is possible it was used by e.g. an addon, but it's fairly simple to update an addon, and we shouldn't let it hold up core development imo. Banditing existing non-working properties for an addon must be considered "subject to change" and caveat emptor.

Leaving these non-working properties / functions in place just in case is likely to create confusion if there is now working LOD functionality, which doesn't use them.

@Mickeon
Copy link
Contributor

Mickeon commented Jan 28, 2024

Just because it breaks compatibility doesn't mean this shouldn't be merged in my opinion, at least.

@lawnjelly lawnjelly force-pushed the lod_scene_side branch 2 times, most recently from c344cc2 to b8e8bb2 Compare February 9, 2024 12:07
@lawnjelly
Copy link
Member Author

lawnjelly commented Feb 9, 2024

Small update:

Looking at @Calinou 's addon I realised he correctly points out that there is a problem caused by running the LOD system in the editor - the changes to visibility as you move the camera can cause unwanted diffs in the scene files.

The addon simply turns off LOD in the editor to avoid this problem (and that is an option too), but it would be nice to allow it, as it previews quite nicely what will happen in game, and allows interactive adjustment of ranges.

Instead here I've added a simple system in the editor saving to notify the World (and in turn the LODManager) when saving is starting and finishing. This allows the LODManager to set all the LOD nodes to temporarily only turn on the first LOD, which ensures no unnecessary diffs in the saved scene file.

(If preferred, I am equally happy to simply turn off LOD in the editor for now and leave this modification to a later PR where it can be more easily bikeshedded.)

More

  • On suggestion from @Calinou I've changed the lod_range and hysteresis to display as float in the inspector, and improved the ranges.
  • lod_range moved from GeometryInstance to Spatial. It seems to make more sense there so any 3D child can be LODed.
  • I've removed the lod_queue from the user interface (but retained it internally). Depending on testing in the field I'm thinking we can probably either expose it or remove.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it works as expected in both GLES3 and GLES2. Other than the UI nitpicks below, it seems to work pretty well.

I'll look into designing an icon for the LOD node.

Testing project: test_lod.zip

  • When setting up a new LOD system, the visibility for LOD's child nodes in the scene tree dock is only updated when you save the scene. Before I saved the scene, all nodes were visible even though their distance was already configured:

image

  • Child nodes' visibility changes while in the editor and when saving, which can be distracting if your node is expanded:
simplescreenrecorder-2024-02-09_22.35.31.mp4

Maybe we should hide the visibility icon for children of a LOD node that inherit from Spatial, as there's no point in manually toggling visibility for those in the editor (changes will instantly be reverted).

  • LOD update speed seems a bit slow when you have lots of instances. This might be an issue if your camera moves fast, as low-detail LODs will remain visible for a while after the camera gets close to objects. Is there a way to speed up the update rate (at the cost of increased CPU utilization)?

@lawnjelly
Copy link
Member Author

lawnjelly commented Feb 10, 2024

I'll look into designing an icon for the LOD node.

That would be fantastic. 👍

When setting up a new LOD system, the visibility for LOD's child nodes in the scene tree dock is only updated when you save the scene. Before I saved the scene, all nodes were visible even though their distance was already configured

I'll have a look at this, it's not stop ship, but could be a quality of life improvement.

Child nodes' visibility changes while in the editor and when saving, which can be distracting if your node is expanded:
Maybe we should hide the visibility icon for children of a LOD node that inherit from Spatial, as there's no point in manually toggling visibility for those in the editor (changes will instantly be reverted).

I'll have a look whether this is possible, no idea where the visibility icon is handled. Can also see pros and cons to this. An alternative (which I could easily put in) is a setting in the editor to turn on and off LOD in the editor, to prevent it becoming distracting.

LOD update speed seems a bit slow when you have lots of instances. This might be an issue if your camera moves fast, as low-detail LODs will remain visible for a while after the camera gets close to objects. Is there a way to speed up the update rate (at the cost of increased CPU utilization)?

Yes, this is exactly what the lod_queue was for, which exists internally but is hidden from the UI .. maybe I should reinstate it. 🤔 I'll do this.

I was worried it would be overkill for users, but it does allow you to set exactly this update rate (by choice of 5 priority queues), and if you have noticed this so quickly, then users will no doubt find it useful. Maybe on reflection in the user interface it could be called lod_priority or something, as the fact that it is a queue is irrelevant internal detail for users.

BTW, what are the thoughts on whether this should be called "LOD" or "LevelOfDetail"? I'm 50/50 on this. We do use some acronyms like HTTP and IK, but I don't mind either and it's real easy to change now (but much harder once merged!).

UPDATE:
You can now turn on and off in the editor (defaults to on):

view_level_of_detail

@lawnjelly lawnjelly force-pushed the lod_scene_side branch 3 times, most recently from 0309c9f to 18942c0 Compare February 10, 2024 10:25
@lawnjelly
Copy link
Member Author

lawnjelly commented Feb 10, 2024

When setting up a new LOD system, the visibility for LOD's child nodes in the scene tree dock is only updated when you save the scene. Before I saved the scene, all nodes were visible even though their distance was already configured:

Ok I've fixed it up so now in the editor, if view_level_of_detail is switched on, if you change visibility manually of a node it will automatically override it (it detects the situation where the number of visible children is not 1, and does a full recalculate of LOD for that node).

This is good in some ways, but it's rather frustrating when you do try and change the visibility of the children and nothing happens, so I'm not entirely sure about it. Perhaps we can improve the user experience here somehow. Maybe an error warning in the output.. 🤷‍♂️ (EDIT : Have added warning message)

It would be nice to ideally grey out the visibility icons for the user experience, however, this may end up being rather complex and bug prone so might be better for a followup PR (plus it would be nice if we can get this complete this weekend as hp I believe is intending to make the next 3.6 beta next week, so leaving some finer details is fine imo as long as we are happy with the general approach).

I've also added code to completely remove LOD node from the queues when it is hidden to save on processing. That was an oversight on my part.

@lawnjelly lawnjelly force-pushed the lod_scene_side branch 2 times, most recently from 0b07076 to 26384c7 Compare February 10, 2024 10:50
@Calinou
Copy link
Member

Calinou commented Feb 10, 2024

Here's an attempt at an icon (it aims to represent a low-detail object, i.e. a circle turned into some kind of hexagon): icon_l_o_d.zip

I've made the sides thicker so it looks more different from the Spatial icon.

image

@lawnjelly
Copy link
Member Author

lawnjelly commented Feb 10, 2024

Here's an attempt at an icon

Fantastic! Thanks. I'll add it to the PR. 👍

I also managed to get the visibility icons to grey out in the scene tree dock. It actually looks pretty good now and is far simpler than I thought it would be, just a few lines of code.

This works much better than the warning message, because it is obvious to the user that clicking on the greyed out visibility will not alter it.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tested the latest revision of this PR and it works as expected.

There are still a few areas of improvement, but these can be left for future PRs:

  • LOD nodes with no valid children or only 1 valid child could emit a node configuration warning, so you know that at least 2 valid child nodes are needed for LOD to have an effect. (A valid child node is one that extends from Spatial.)
  • When initially loading a scene, LOD is progressively applied with the selected priority, which can make it obvious in certain cases:
simplescreenrecorder-2024-02-10_19.21.43.mp4

Watch the video at 0.5× speed by right-clicking it to make it more noticeable.

Perhaps LOD priority should be forced to its maximum value for a few frames when the engine initially loads. That said, polished games generally don't immediately boot in a playable scene (they generally hide things with loading screens), so it may be a non-issue.

doc/classes/LOD.xml Outdated Show resolved Hide resolved
Comment on lines +1353 to +1359
// Allow a generic mechanism for the engine to make changes prior, and after saving.
if (editor_data.get_edited_scene_root()->get_tree() && editor_data.get_edited_scene_root()->get_tree()->get_root()) {
edited_world = editor_data.get_edited_scene_root()->get_tree()->get_root()->get_world();
if (edited_world.is_valid()) {
edited_world->notify_saving(true);
}
}
Copy link
Member

@Calinou Calinou Feb 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook could be really useful to have in editor plugins and @tool scripts, also in 4.x 🙂

There are multiple instances where I've wanted to make editor-visible changes that aren't serialized to scene files outside of LOD.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I was surprised there wasn't something like this already. Although it may have to be done carefully in some cases to avoid race conditions.

Add scene side discrete level of detail.

New node `LOD` for UI, and `LODManager` within `World` for automatically updating child visibilities based on distance from cameras.
@lawnjelly
Copy link
Member Author

LOD nodes with no valid children or only 1 valid child could emit a node configuration warning, so you know that at least 2 valid child nodes are needed for LOD to have an effect. (A valid child node is one that extends from Spatial.)
When initially loading a scene, LOD is progressively applied with the selected priority, which can make it obvious in certain cases:

Yes, as you say, easy to do in follow up. 👍

@lawnjelly lawnjelly modified the milestones: 3.x, 3.6 Feb 12, 2024
@akien-mga akien-mga merged commit ae7dfd1 into godotengine:3.x Mar 8, 2024
13 checks passed
@akien-mga
Copy link
Member

Thanks!

@Zireael07
Copy link
Contributor

Woot woot, issue closed after 4 years!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants