Batching has two purposes:
-
Improving performance.
Batching is faster than doing one drawcall per cube. Draw calls are expensive, so packing your vertex data together and drawing it all in one draw call will be faster.
-
As an investment.
In the case of cube worlds, batching also works as an investment. Looping through the volume of an entire cube world is slow as hell. A 1000x1000x1000 world is already 1 billion blocks to check. This is a huge waste to each frame, because in the end only a tiny mesh will be generated for this world, as 99.99% of the world will be either solid ground or air. Hence, we precompute the mesh once and store the mesh, giving us the flexibility of cube worlds with the performance of a mesh.
The problem occurs when you want to change a single cube in the world. It’s too slow and expensive to try to modify the mesh based on the cube added/removed, so the only real choice is to regenerate the mesh from scratch. This will cause a massive spike even when just a single cube is changed. The solution is to split up the world into chunks, so that you only need to regenerate the chunks affected by the changed cube instead of the entire world. In theory, this introduces a trade-off, as we now have more than 1 draw call, but even 10 or 100 draw calls isn’t really that significant. The main point is to avoid having to regenerate the mesh each frame, which is still the case.
You get further problems if you want to update the terrain every frame. The answer to this is: don’t. If you want to animate the terrain textures, you can either update the texture to animate all cubes identically, or do the animation in the shader based on an ID (this way all cubes can be animated individually).
Instancing is NOT a good choice for a cube world. You want to only draw the faces that are between a solid block and an air/transparent block. In 99% of all cases, you won’t even be drawing an entire block. Hence, only being able to control visibility with the granularity of an entire cube with all faces visible is not good enough. For a simple case of a flat floor, this will draw 6x as many triangles as a face-based mesh. In addition, instancing is not as effective for small meshes. A cube is only 24 vertices. You should try to have have batches of at least 100 vertices or you’ll get reduced GPU performance drawing all the tiny meshes. Basically, on the CPU side instancing is much faster as it’s just one draw call, but on the GPU side you’re still drawing a crapload of small meshes, which GPUs aren’t good at drawing.
TL;DR: Use chunking, and if you need to animate the texture of a cube either update the texture or use a shader to animate it.