Multithreaded LOD in C++ for Godot

Arman Elgudzhyan |  30 December 2020  |

Last modified on 2021-06-30

See here for the (mostly up to date) older post for the previous version of this system.

Godot 4.0 is coming, and we’ve already seen some examples of automatic LOD generation. However, in the meantime, I’ve updated my Godot 3.0 LOD plugin to be multithreaded, as well as a few other quality of life improvements.

The system is available on my godot-extras repo here. There is also an example project which contains the addon itself.

Addon and LODParent

You do not need to add a patch to register the LOD multipliers anymore. However, if you use the GIProbe LOD I still recommend using the patch to have 4 GIProbe blending.

You add the addon as you would any other, enable it, and you will have a new node called a LODParent which inherits VisualInstance and has the lod.gdns script attached. For the other LODs, you will still need to attach the respective script manually by adding it from the addon folder.

LODManager

All the threading and various global properties are controlled from a LOD manager, which is a scene that is registered as an AutoLoad when the addon loads. As you can see, you have checkboxes that will determine if your multipliers, AABBs, and the fetched camera FOVs are updated on every loop. In most cases, this is not required and will slow down the thread, but I’ve left it here if you choose so.

There are also functions you can call on the manager.

This means you can update the multipliers at runtime. Once you change them in the manager, you will need to call .updateLodMultipliersInObjects(). If you have done so from the ProjectSettings you will need to call .updateLodMultipliersFromSettings() which will automatically call .updateLodMultipliersInObjects() once it fetches the values from the settings.

You can also update the AABBs manually using .updateLodAABBs() and the FOV by calling .updateFOV(). If you have updated the FOV and want to update the AABB, you will need to call .updateLodAABBs() manually afterwards.

Using .stopLoop() you can manually stop the LOD thread.

The Objects Per Frame number is used only if you disable multithreading. To avoid hiccups, the number of objects processed per frame is broken into chunks of an adjustable size which you can set here.

The Debug Level field lets you select multiple levels of info to be printed to the console. Beware of console spam if you have many objects and set a high debug level.

Screen-based distances

Instead of some basic default distances and manual inputs, the system can now make a worst-case estimate of how far your object should be. This is possible for normal/mesh objects, MultiMeshInstances, and GIProbes.

It is also possible with Lights.

It will calculate the longest axis on the AABB of all combined LOD objects/children and then get how far they have to be at the provided screen size percentages (e.g., LOD2 appears once the object is less than 7% of the screen) based on FOV and the longest axis. Keep in mind this means you must have a valid AABB for at least LOD0. Notably, this means you cannot use a Spatial for LOD0. It seems to visually display an AABB in the editor, but it does not inherit VisualInstance so it is not possible to actually access it.

Also, specifically for lights, keep in mind sometimes the AABB for the light may be larger than you expect. If you have a spotlight with a long range (as opposed to a strong energy), you will have a long and thus large AABB and longer distances than you may expect. Make sure to test!

Keep in mind if you change the object sizes or FOV, the distances will not be updated automatically. Updating the AABBs from the LODManager will update the distances in all objects, though.

A new test

Finally, here is a new, slightly different test for the update!


The graph from the end of the video:

Things to keep in mind

Next steps/considerations