DevLog #004 – Generating Terrain

Part of a series of posts in which we write a voxel engine from scratch for Android.

It’s impossible to talk about terrain generation without mentioning Perlin Noise; a very famous algorithm developed by New York University professor, Ken Perlin; which in simple terms, takes coordinates in n dimensions (x,z or x,y,z depending on implementation) as input and returns a value between -1.0 and 1.0 (or [-sqrt(n)/2, sqrt(n)/2] if you want to be pedantic, which I don’t) which is predictable and repeatable, with a smooth gradient between values at any given coordinates.

This is important because it allows us to say “give me a value at this position” and we know we’ll get something sensible instead of just a random number – and because it’s predictable and repeatable, we can use it to generate terrain heights at any given position, in any order, and the result will be natural curves.

Just Get On With It

So last time, we filled our chunk with random blocks, and now we’re going to use our noise function to generate some natural looking terrain. We’ll start by defining some parameters that will give us some control over the results..

float mag=0.36f;                 //Magnitude of features (in y)
float median=0.33f;            //Median height of features
float scale=0.9f;                  //Scale of features (in x,z)
float modscale=0.5f;           //Scale of modifier (in x,z)
float rockmag=0.5f;            //Magnitude of rock features (in y)
float rockmedian=0.38f;     //Median height of rocks    
float rockscale=0.7f;           //Scale of rock features (in x,z)
float sandlevel=0.25f;        //Height at which to add sand

The values here are arbitrary, and they can be tweaked to produce different results. We then pass these parameters to our Chunk.addTerrain() method which fills the chunk with blocks using our noise function.

I won’t bore you with the code, but it produces something like this..

You might notice, we’re also adding variations in rock strata (so the player has something to dig for) but it’s about time we start using proper textures instead of these ugly place-holders…

Our chunk is starting to look a little better, but it’s only one chunk! Not much fun to explore on its own. Before we add some neighboring chunks, let’s strip out any unseen faces by checking each block in our Blocklist for neighboring blocks before rebuilding the mesh…

Yikes. That’s going to come in handy when we’re drawing the whole map. We can vastly reduce the triangle/vertex count this way, which will also reduce the amount of memory we need to store our terrain meshes.

Finally, we can add some more chunks to our world, and everything starts to make sense..

If you’re curious about those debug values in the screenshot; we have (top-left) 36 BigChunks (BC), 288 Chunks (CH), over 400,000 Blocks (BK) and 9.6 million Triangles (TR – which is 12 per block if we weren’t face culling)

On the right you can see it’s running at 60FPS with 28 Draw-Calls (drc – which includes the debug text) and it’s only rendering 11,464 triangles (of 9.6 million total)

Relax – We’re not actually storing 9.6 million triangles in memory! We optimized them out during the building process. 🙂

Next time we’ll talk about streaming chunks – because we can’t possibly load them all into memory at the same time.