DevLog #002 – Making the First Block

Part of a series of posts in which we write a voxel engine from scratch for Android.
If you missed the first entry in this DevLog, we talk about how we use chunks to store blocks in memory. It’s highly recommended you start here.

Before we can start generating terrain, we have to think about the most efficient way to render a single block – because each chunk in our world will have up-to 4096 of them, and given our map size, we’re going to be dealing with over 18 million blocks. If you remember our commitment in the previous post to having this run at 60fps (on mobile), it’s time to get serious about optimization.

Draw-Calls Are Costly

Simply put, a draw-call is an instruction to the graphics card, and the more of these you have each frame, the slower things get – because there’s a limited amount of bandwidth to pass data between the CPU and the GPU. On modern PC’s you can get away with hundreds or even thousands of draw-calls, but on mobile devices, we need to stick to around 60-100 per frame to avoid significant slow-down.

If we’re targetting 60fps, we only have 16.33 milliseconds to draw each frame. A very tight schedule! So how on Earth are we going to render all these blocks with less than 100 draw-calls?

Batching

Batching is a technique for combining renderable elements (3D models, sprites, or in our case, blocks) that share a common texture into a single draw-call. More accurately, changing the texture is a draw-call, and rendering an array of polygons is a draw-call – so if we can combine all the textures into one (Atlasing) and all neighboring blocks into the same 3D model, we can drastically reduce the number of instructions sent to the GPU per frame.

There are 2 types of batching. Static Batching combines everything once (at build-time) and Dynamic Batching re-creates batched meshes at render-time. We’ll be using Static Batching whenever new terrain is generated, because our engine does not have Dynamic Batching, which is more CPU intensive anyway.

Texture Atlasing

A Texture Atlas is just a big texture made-up of smaller textures, and they allow for more efficient batching of draw-calls, because everything that shares the same texture can be batched together. The only downside is that applying textures to our blocks is slightly more complex.

Our atlas is 1024px, and contains 64 textures at 128px each

Each type of block now needs to know how to find the texture for each face in our atlas. We do this by dividing the UV coordinates by the number of columns, and calculating an offset with some modulo for wrapping.

double scale=1.0/columns;
double offsu=(subtexture*scale) % 1.0;
double offsv=Math.floor(subtexture/columns)*scale;

u=(u*scale)+offsu;
v=(v*scale)+offsv;

Some block-types will use one sub-texture for all faces (like the pink cube above), and some will have different sub-textures for the top/sides etc.. but we only need to calculate this once per block-type.

Now we have blocks sorted, in the next post we’ll be creating our first chunk! What fun.