Understanding Batching in Godot 4

Godot Version

4.2.2

Question

In my project, I have a single mesh that acts as the base level with a poly count of around 30k.
To surround this level, I have placed many instances of the same, low poly pillar mesh.

In the scene there are 3 materials:

  • The player model material
  • the floor material
  • the wall material, also shared by the bound pillars

This results in about 50 draw calls when playing the scene.
To my understanding, since these pillars are all an instance of exactly the same mesh, they should be batched and drawn together. As of Godot 4 this seems to be done automatically according to the docs, but maybe I am missing a project setting.

How come when disabling the pillars, the draw calls get halved to 25?
In this post, the author mentions having only 20 draw calls, despite having 500 instances, which is a lot more than I have instances of this pillar. He’s not using multimesh either.
Same goes for when I enable my grass multimesh, which has about 3000 instances of the same mesh, yet the draw calls go up by about 600.

Could anyone shed some light on this?

The post mentioned believes the LOD set to an aggressive mode is what’s saving the performance. LOD meshes would increase the draw calls so I doubt 10-20 is their actual draw call count.

I would use a MultiMeshInstance to explicitly create what you want. With low poly rocks it’s probably worth a multimesh since low poly models has little GPU impact and multimesh will push the majority of processing onto the GPU.

Are the copies made within a Blender scene? If so, check whether instancing is actually used in Blender. You don’t want the geometry to be duplicated for each instance:

https://github.com/godotengine/godot-proposals/issues/7366#:~:text=Bistro%3A-,horrible%20optimization,-I%20don’t%20know

1 Like

Thank you for the reply. That may be a good choice, since the player will never interact with these meshes.

However, according to my interpretation of the docs which state that automatic batching of identical meshes should be enabled by default, does it really make sense that every rock adds a draw call?

Instancing is only possible if the mesh resource is the same and it uses the same material. If there’s even a slight difference in the mesh resource (e.g. because it’s marked as Local to Scene or is painted with vertex colors unique to that instance), instancing isn’t possible.

I believe lightmapping also prevents instancing of static lightmapped objects for a similar reason, as each object will use unique UV2, but I haven’t checked.

1 Like

These are all made by duplicating the same scene instance, so everything should be identical except for the transform.

I was using a voxelGI in this scene though, do you know if the same is true for this as it is for lightmapGI? i doubt it since the voxelgi does not use UV2. I will check asap if it reduces the draw calls

They are not; each of these were placed by duplicating the original scene instance in my level scene in godot. the rock was made in blender and exported to gltf.

I imagine the mesh instances, if they are using the same mesh source and materials (in the same order, etc), won’t batch automatically due to them being in different scenes as I could see it being the that alone would make them branch.

Can confirm: Having the meshes not as individual scenes does not impact draw calls

2 Likes

Sorry to raise this from the dead - I am investigating my own issues with batching in Forward+ (Godot 4.3.1). Are there any other circumstances that batching wouldn’t occur? In my case, I am rendering 524 Quad meshes (all use the same mesh resource, no duplication), with 9 material variants. All the materials are StandardMaterial3Ds, each with the same texture, but different UV offsets. Once again, there’s no material duplication happening here, and both the Quad mesh and material override are set at runtime in the ready() function. I’m using a texture atlasing approach. I’d expect 9 drawcalls for the 524 Quad meshes, but I’m getting around 100.

Is there anything else I need to consider?
(full post here)