Optimizing 3D games in Godot 3.x 2021-07-20 on Kenneth Dodrill's blog

When I made my first commercial game, my brother lives in a canyon, I had a difficult time with performance.

My main problem was that my game world was large. To capture the entire world through the first-person camera, I had to multiply the view distance by 15. My game world was simply too big for Godot / OpenGL to handle well. Initial design of your game world is extremely important for performance.

I also didn't really understand how to make low-poly models at first. I used Blender's tree generator to make trees, which have a high poly count by default. Eventually I figured out how to lower it, and found out how to make the game perform a lot better - by using LOD, or level of detail. This is what you'll see in games where models are replaced with lower-quality ones the farther you are from them, where when you get closer they are replaced with high-quality versions. This can help performance by a lot. In my game, I was running the game at around 40-60. I was getting around 120fps after implementing LOD using an addon for Godot. Some game engines have some kind of default LOD to help performance, but Godot 3.x does not have this (Godot 4.0 will).

The LOD addon I used includes a spatial node that takes 2-3 scenes and will replace them depending on developer defined distances. I then used a scatter addon to distribute these. Despite the tremendous performance boost, the editor still ran really slowly because the LOD addon doesn't run in it. It also took very long to save because the scatter addon produced so many nodes.

Something you will see on Godots docs about 3D performance is that they recommend using instancing. You should be very careful with instancing. Instancing uses a multimesh node rather than several of the same node, which means that when one of those meshes is in the viewport, it will render all of them. This can be very taxing if you are using it for higher poly meshes. It really is something that you need to test for each mesh that you want to scatter. I found that grass and rocks did fine with instancing because it was rare for the viewport to not be pointing at something without a rock or grass. However, for each of my tree scatters I did not use instancing because looking at just one of them would render the rest. Using individual meshes means they would only be rendered when the viewpoint is pointed at them. This usually meant less trees, which means better performance.

Godots docs also recommend baked lighting. Unfortunately, when I tried using it, the current version of the engine had a bug involving baked lighting. I didn't really need dynamic lighting, but I didn't try baked lighting again after that. It's possible that this could help performance a lot, but it is also supposed to substantially change the visuals. When I tried baked lighting, it did not play well with my dither shader either, which possibly relies on dynamic lighting.

I think if I was a more impatient person / developer, I would've considered switching game engines. There are just too many good things about Godot to do that. 3D performance might not be the best, but I think it does a good job with it as long as you're willing to learn different ways to make your game perform better in general. You can also then take that knowledge and apply it to other game engines. I eagerly await Godot 4.0 with its Vulkan support and automatic LOD.