Limiting draw calls by combining meshes

Godot Version

4.2.2

Question

Hey guys, is there a way to procedurally generate a bunch of meshes while using one material and combining them all into one draw call?

var mesh_data = []
const OPAQUE = preload("res://material/opaque.tres")

func _ready():
	mesh_data.resize(ArrayMesh.ARRAY_MAX)
	mesh_data[ArrayMesh.ARRAY_VERTEX] = PackedVector3Array(
		[
			Vector3(0,0,0),
			Vector3(1,0,0),
			Vector3(1,1,0)
		]
	)
	mesh = ArrayMesh.new()
	
	for i in 30:
		mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_data)
		mesh.surface_set_material(i, OPAQUE)

This is what I’ve got so far. The vertex are the same and they are using the same material but it does a draw call for each one.

What I am trying to do is create a voxel world like minecraft where I draw a crapload of quads onto the scene and change textures for each one. Is this possible to do with one material and draw call?

With some fancy data you can use MultiMeshInstance3d for different textures.

When trying to create a voxel world the first thing you must do is optimizing meshing, you will not draw every single cube, you will draw one chunk at a time. Get familiar with ArrayMesh as that my be your best tool.

I have the same requirements as you, where a chunk mesh has limited draw calls while also letting each block have a different texture. You could have 1 draw call for each type of block using multimeshinstance as gertkeno suggests.

The problem you are facing is that you add 30 surfaces which need a draw call for each one. You could limit it to one surface, but then you can’t have different textures for each block. So how would you solve it?

  1. create each block separately and give each a different texture. This will not solve your problem to reduce draw calls.
  2. Create an atlas (as I did) and manipulate the UV cooridnates to have the correct texture displayed on each block.

You may know this, but creating an atlas involves merging all textures into a big texture and letting the game render only part of the texture. In my case I am creating the atlas dynamically.

You can see my solution here: CataX/Scripts/Chunk.gd at c1a65f8ac647af392742830ea2e4c14e28ca9a1b · Khaligufzel/CataX · GitHub

In your case you may be able to reduce the complexity by having randomly generated chunks (as opposed to me, where I have pre-defined chunks) and generate 1 mesh for the entire chunk (as opposed to 1 mesh for each y level)
In my case I have 1 surface per y level, so a maximum of 21 draw calls per chunk (i am using an arbitrary amount of 21 y levels)

So your next steps would be:

  1. figure out ArrayMesh as gertkeno suggests. The SurfaceTool may make it more easy for you also. This step allows you to define all required vertices so you have the shape of your mesh
  2. figure out how to plot the correct texture coordinates of an atlas onto the blocks’ uv coordinates.

Hey guys, so I can do this in unity with a texture array, https://www.youtube.com/watch?v=Q60cdwZDyjE&list=WL&index=2&t=637s, and it works, I can draw hundreds of meshes with one draw call.

Just not sure how to do it in godot. When I combine meshes it adds a new surface to the mesh and I assign it the same shader but it shows a draw call for each surface.

It’s no biggie, just wondering if it was possible or not. Thanks for the replies.

That video is an example of a shader and GPU instancing. In Godot it would be MultiMeshInstance3D and a shader material. The shader would happen to make use of a sampler2DArray uniform.

Hey snipercup I see how you are combining them for each Y lvl but does this actually limit your draw calls?

How is your performance? How big of map / render distance can you have? I noticed you are using colliders, I think how Minecraft did it was they calculated all the physics themselves. Thx for reply.

Well, each y level has one surface, so it would be one draw call per y level, right? Unless I’m misunderstanding something. The reduction is having one surface for all blocks on a level instead of 1024 surfaces for a level (one per block)

My performance is fine. I load about 9-12 chunks at a time. My maximum render distance is fixed since I’m making a top-down game. This has some side effects like hiding the floors above the player, further reducing draw calls.

Further optimizing the colliders was tricky. I was mainly concerned by the amount of colliders since they are added to scene tree and force a sync between the threads and the main thread. I came up with a basic algorithm to combine the colliders, which seems to work.