Exploring the game development world: Drawing a voxel terrain

Miguel Celedon
5 min readJul 22, 2017

--

A chunk of a voxel world

In the last post we explored the theory about terrain generation and decided how we are going to draw our voxels in our terrain. Now we are ready to write code to actually draw something in our game.

As we are going to create a mesh at runtime we start off by creating an empty GameObject and add it a script, lets say “ProceduralTerrain”.

To draw a custom mesh it’s really simple in Unity, just modify the MeshFilter on the GameObject we want.

Mesh mesh = new Mesh();
GetComponent<MeshFilter>().mesh = mesh;
mesh.vertices = myVertices;
mesh.triangles = myTriangles;
GetComponent<MeshCollider>().sharedMesh = mesh; // To add collision

And that’s it, we link out mesh to the object and then we can add our vertices there as we like, and the triangles are just indexes of the vertices array to define their order. That’s the easy part, now we must define how we’re going to add those vertices.

Starting as a graph

We will decide what faces to draw using a graph model, so we could start drawing from one block (a root), and then jump over every neighbor and, at each one, do the same over and over.

Example of graph defining four connected blocks

A graph has nodes and edges, nodes ideally stores a value, for us this will be data that each block will store, for now it’s enough to store type of block it is:

public enum BlockType {
Air,
Dirt,
Stone
}

These are the most basic types we could find in any exploration game, Air is merely a value for our enum to denote emptiness, that is our null , for now we will only use Dirt to denote existence because we are not defining types yet, we just want a terrain mesh.

Now, I want each block to be aware of where it is and who are their neighbors, so I added them the location in the voxel grid:

Vector3i is just a copy of the Unity’s vector but using only integer values, just to simplify the code.

As we can see, there is an indexer where we can get the block’s neighbor positions given a CubeFace direction, which is nothing more than an enum with the values:

public enum CubeFace {
Up, Down, North, South, East, West
}

Because I just don’t want to go adding simple math operations wherever I need to check neighbors.

Now, to traverse the graph and draw each block’s face based on their neighbors I wrote this piece of code

Initial drawing code

This code is away from perfect, as we will get some duplicated vertices because as we are avoiding inner faces, we are still repeating vertices at the corners in the blocks but it’s something anyway.

The Draw method is a breadth-first search for an algorithm.

Arbitrary chunk of blocks

By filling blockMap with arbitrary data we could create this chunk. Now let’s make it bigger and add some mountains.

Adding Perlin Noise to the mix

We can start adding some mountains and hills using the Perlin Noise algorithm to create a heightmap where we can fill blockMap accordingly.

Now, you might be wondering “how are we going to use the Perlin Noise without knowing how it works?”, well, it’s because Unity already has an implementation for us! It’s Mathf.PerlinNoise.

This function accept two floats (x, y), and returns another float between 0 and 1. But keep always in mind that the input values should be between 0 and 1 as well, as the implementation will apply a modulus-1 operation that will get rid of the integer part, so if we run the function with (0.5, 0.5), (1.5, 1.5) and (2.5, 2.5) we will get the same output.

So it’s easy to set the inputs for the function to be each coordinate (x, z) divided by the terrain’s total width and depth respectively so we always get different results.

Functions to generate the height map

Basically, we are traversing each (x, z) pair of our block terrain, convert each coordinate to [0, 1], then use them to run PerlinNoise and linearly interpolate the result between [-maxY, maxY], then we just need to set the blockMap to accordingly to our height map. Without calling the flatCenter() and using a small terrain we got something like this:

Pelin chunk

Isn’t it cute? Our first piece of Minecraft terrain. Now, using a big-size space and using flatCenter() to keep a place of quiet we got this:

A whole cube terrain

Ok, we go mountains, but they are too much pronounced, we need to leverage things to get a more flattened world and, if possible, flat at the center. The thing here is that our noise is too drastic, passing from 0 to 1 in so little distance. So the answer here is to scale up the noise space by scaling down our inputs for the PerlinNoise, so we modify the scale variable to 0.07f and we won’t need anymore the flatCenter() function. Giving us something like this:

Flattered Perlin terrain

No we are getting something pretty, I almost feel like in a voxel world already. Now let’s make a much, much bigger terrain!

Too many vertices!

Oops! Looks like we have a limit of vertices that a mesh can have… makes sense, because I don’t want to imagine the power needed to keep track of collisions with so many vertices and faces for example. So that’s why voxel games tends to separate the terrain in chunks, we will have to do the same here and separate our terrain in consistently sized chunks.

Now we have a next goal with our terrain, separate it in chunks to handle it better. Next, we could look for ways to make the world less blocky, or explore other gameplay concept, I don’t know, now is time to write my stories while I code and solve more problems, I don’t know how much time will it take to post new stories but I hope it doesn’t take long. Thanks for reading so far!

--

--