• 3D
  • Godot 4: Ways to optimize a tiled map in 3D

Hi.

I'm playing with tile based 3D levels in Godot 4 alpha. I'm struggling with performance hits.

I tried to use GridMap node. It was very promising until I got a huge performance drop after making a place with bunch of trees. And what I noticed is that Performance is going down with when GridMap is getting bigger. Also I had issues with tile offsets, rotation and collisions. I read that GridMap is creating a new mesh(es) and does not work with transforms applied to tiles. Moreover, I cannot use visibility ranges nor bake occluders for GridMap.

Today I've created a prototype of custom importer for TMX files (Tiled Level Editor) and changed a way of generating a 3D level.

Instead of using GridMap my importer is creating a new scene with nodes placed in correct positions. Each node corresponds to a tile from TMX layer.

TileSet is a simple scene with childs, where each node acts as a tile by it's number. These tiles are duplicated during import, and placed on a new scene. Without duplication I cannot add objects to a new scene.

Advantages:

  • rendering seems to by way faster than GridMap
  • occluders can be baked with OccluderInstance3D (it does not work for GridMap)
  • visibility ranges can be set per tile
  • no problems with tile offsets, rotation, collisions; tiles can be positioned properly
  • no issues with updating - just do reimport and reload a scene (GridMap seems to be buggy and can give strange results sometimes)

Disadvantages:

  • this approach is memory consuming; map of size 256x256 is crashing Godot while loading due to out of system memory (16GB)
  • for a map of size 128x128 baking occluders is crashing due to out of memory (but not all system memory is consumed; it probably hits some kind of internal heap limit)
  • TileMap scene created by importer contains large amount of nodes, which can introduce a new class of performance issues

So as you can see I can't build a medium sized world, and I'm trying to find a way to optimize this. I have an ideas about splitting maps into smaller chunks (that's obvious), reducing polys, using portals and rooms (when they will be ported to 4.x), but I wonder is there anything else I can improve? I also thought about doing some clusering with MultiMeshInstance3D, as a tradeoff between flexibility and performance, somehow similar to GridMap's octants but preserving ability for baking occluders and apllying visibility ranges with fades.

I'll appreciate any ideas and hints.

Regards, Marcin

I also thought about doing some clustering with MultiMeshInstance3D, as a tradeoff between flexibility and performance

Rendering with MultiMeshInstance3Ds is not better. It worsens as the cluster size (MultiMesh area) increases. But the scene is loading faster (also in Editor), probably because of smaller amount of it's nodes.

With Multimeshes I can bake occluders without a crash, but it seems that OccluderInstance3D does not work with MultiMeshInstance3D - there is no visible occlusion mesh.

It looks like creating scene with pure nodes is best for renderer, but still not efficient for creating bigger worlds. I read about using VisualServer directly, but don't know how to use it yet. I wonder how other engines solves this problem.

I've checked this on Godot 3.x. Map size 128x128 almost freezes the editor - the performance is about 1fps or less. So I must say that Godot 4 is way better in terms of handling many objects, even if rendering pipeline seems to be slower on my system when comparing to Godot 3.4 @ GLES3. Good work! :+1:

Strange. I'm pretty sure GridMap has it's own form of optimization in some sort of BSP structure. I've only tested it with small maps, though, like a few dozen blocks. It might also be your models or collision objects. I noticed the new Godot Physics in 4.0 doesn't like detailed collision meshes. It was fine in Bullet with 3.x, but the same detail mesh in 4.0 dropped FPS for me down to 1 fps. I would recommend removing the collision mesh for testing, or using a simple cube that will be easy to calculate. Also, if the tree has transparency, that can be an issue as well. For leaves or foliage, it is better to use alpha scissor textures, as these use the opaque pipeline and are essentially as fast as non-transparent objects.

I will replace objects for tiles with something less random. Tree is too complex - it has about ~71k vertices, mostly for leaves. This wasn't a problem, until my 6-yrs son decided to create a forest. Kids... But since he already gave me "the job", I'm trying to get better results under some stress.

I will try GridMap again and compare it on the same data. Previously the map was handcrafted by kid inside Godot Editor. And there was a big garden with trees.

I read few days ago about using alpha scissors. I'm aware how Godot handles transparent materials. In this examples I'm only testing fading by distance to get nice "roll off".

Thanks for tip about collisions. I noticed something similar in previous test project (a ball bouncing fast inside a collision mesh killed the performance).

71K verts for a tree?! Yeah, that's probably your issue. It should be like 1K at the most, or even less if you want to have a bunch of them. You don't want to model each piece of grass or leaf as this doesn't scale. Use an alpha scissor texture, at medium/far distance it will look the same or even better.

Yes! A random picked model from some free site, directly imported to the project. But thanks to this I have some maybe useful (who knows) findings. Finally I'll replace it with low poly model.

for a map of size 128x128 baking occluders is crashing due to out of memory (but not all system memory is consumed; it probably hits some kind of internal heap limit)

If you have time, please try to reproduce this in a minimal project and create an issue on GitHub :)

If you have time, please try to reproduce this in a minimal project and create an issue on GitHub :)

OK. I'll try, but I'm afraid I will have to include a little tree ;)

I have trouble with reproducing the issue on a minimal project. I need more time. I can also try to compile Godot with debugging symbols and give backtrace at least.

Meanwhile I have a question. Is everything ok with internal storage of imported objects?

I have a source files:

39M scene.bin 124K scene.gltf

They results in a huge file in .godot/imported: 1,2 GiB scene.gltf-d232e2b8302efc4c14a209f391acc924.scn

Scene has ~365k verts.

They results in a huge file in .godot/imported:

Godot generates LODs and other data on import, so it's expected that the imported file is larger. Try disabling the various generation options in the Import dock and see if the imported file becomes smaller.

2 months later

I wonder if you can share your TMX importer, this sounds like something useful to many other people...

@Zireael I would like to, but it is quite incomplete for now (just a kind of test, a proof of conecpt), and only available for Godot 4.x. If I have some time, I'll do some tweaks and post the code on GitHub.