Adventures in Programming: 1-1 Voxel Terrain Generation – Rendering and texturing a block.

Hi. I haven’t posted much here recently, as I’ve been busy starting a new job at a small vr games studio for my placement year. However, over the next year, I plan to start posting semi-tutorial write ups on projects I am working on. I will be calling this series ‘Adventures in Programming.’ In the series, I will be writing about several learning projects I will be working on. The first of these being a basic voxel terrain generator in Unity.

In this first post, I will be covering the initial steps that are necessary for any voxel generation. The first of these being how to render a cube. A beginner in unity might say that this is as simple as right-clicking in the hierarchy and creating a primitive cube, however, generating a large map out of unity primitives would be extremely inefficient and would take a large amount of time to generate. Hence, we must generate a procedural mesh instead. Generating a procedural mesh will allow us to group large sections of blocks together into a single mesh, these large sections are called Chunks.

1.1.1 Generating a Cube

A cube is made up of 8 vertices, one for each corner, and 12 triangles, two for each face. The vertices are as follows;

public static Vector3[] vertices =
{
new Vector3(0,0,0),
new Vector3(1,0,0),
new Vector3(1,1,0),
new Vector3(0,1,0),
new Vector3(0,1,1),
new Vector3(1,1,1),
new Vector3(1,0,1),
new Vector3(0,0,1)
};

Related image

Each face is then made up of two triangles, which are each made up of three vertices. The numbers for each triangle represent the index of the vertices.

public static int[] triangles =
{
0, 2, 1, //face front
0, 3, 2,
2, 3, 4, //face top
2, 4, 5,
1, 2, 5, //face right
1, 5, 6,
0, 7, 4, //face left
0, 4, 3,
5, 4, 7, //face back
5, 7, 6,
0, 6, 7, //face bottom
0, 1, 6
};

We can then generate a cube using these arrays by adding the vertices and the triangles to a meshFilter.mesh using the following code;

        Vector3[] vertices = new Vector3[36];
        int[] triangles = new int[36];

        for (int i = 0; i < Block.triangles.Length; i++)
        {
            vertices[i] = Block.vertices[Block.triangles[i]];
            triangles[i] = i;
            Debug.Log(vertices[i]);
        }


        //Build the mesh
        mesh.Clear();
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.Optimize();
        mesh.RecalculateNormals();
        collider.sharedMesh = mesh;

This will result in generating the pink cube on the left. When a material is put onto the cube, it won’t apply a texture yet, and will instead be the colour of the pixel in the corner of the texture. We will fix this next.

1.1.2 UV mapping and texturing

When generating a voxel world, it is important to ensure we minimise the number of draw calls we use to prevent low frame rates. Because of this, we want our entire terrain to use a single material, however, we still want to be able to have different textures on each block/block face. To achieve this, we must use a texture atlas.

Here is a basic atlas that I have created, it only has a few textures on it at the moment, however, you may also create your own. If you are creating your own, you may need to change some of the following code to accommodate the resolution you have chosen, I will be sure to point out where this will be relevant. My atlas is 1000×1000 pixels, with each “face texture” being 100×100.

The texture atlas will simply allow us to use a single material for our entire terrain, it will also make life easier if you want to change your textures at some point.

When UVing our block, we will be setting a Vector2 position on our texture atlas for each vertex. For now, we will map each block to be the grass texture in the top right hand corner, however, we will be able to offset this value by a Vector2 to change the texture.

Here is the code for each vertex’s UV co-ordinate. Note that the bottom-left of the texture is represented by the position (0,0), so we are starting from the position (0,1) (top-left). These are the numbers you will have to change if you create your own atlas. Here, since my texture resolution is 1000×1000, and each ‘face texture’ is 100×100, each corner of the texture is represented by a multiple of 0.1f. As 1000/100 = 10, and 1/10 = 0.1f.

public static Vector2[] UVs =
{
new Vector2(0.0f, 0.9f), //Front
new Vector2(0.1f, 1.0f),
new Vector2(0.1f, 0.9f),
new Vector2(0.0f, 0.9f),
new Vector2(0.0f, 1.0f),
new Vector2(0.1f, 1.0f),
new Vector2(0.1f, 0.9f), //Top
new Vector2(0.0f, 0.9f),
new Vector2(0.0f, 1.0f),
new Vector2(0.1f, 0.9f),
new Vector2(0.0f, 1.0f),
new Vector2(0.1f, 1.0f),
new Vector2(0.0f, 0.9f), //Right
new Vector2(0.0f, 1.0f),
new Vector2(0.1f, 1.0f),
new Vector2(0.0f, 0.9f),
new Vector2(0.1f, 1.0f),
new Vector2(0.1f, 0.9f),
new Vector2(0.1f, 0.9f), //Left
new Vector2(0.0f, 0.9f),
new Vector2(0.0f, 1.0f),
new Vector2(0.1f, 0.9f),
new Vector2(0.0f, 1.0f),
new Vector2(0.1f, 1.0f),
new Vector2(0.1f, 1.0f), //Back
new Vector2(0.0f, 1.0f),
new Vector2(0.0f, 0.9f),
new Vector2(0.1f, 1.0f),
new Vector2(0.0f, 0.9f),
new Vector2(0.1f, 0.9f),
new Vector2(0.0f, 0.9f),//Bottom
new Vector2(0.1f, 1.0f),
new Vector2(0.0f, 1.0f),
new Vector2(0.0f, 0.9f),
new Vector2(0.1f, 0.9f),
new Vector2(0.1f, 1.0f)
};

Now that we have our list of UV positions, we need to map each UV co-ordinate to its vertex. We can simply do this with the following code:

Vector2[] uvs = new Vector2[36];    
for (int i = 0; i < uvs.Length; i++)
{
     uvs[i] = Block.UVs[i];
}

We will also need to assign the uvs array to our mesh. We do this by adding the following line of code before optimising the mesh.

    mesh.uv = uvs;

Now that we have UV mapped our block, when a material is assigned to the block, each face should be textured with the grass texture from our atlas.

That is all I will be covering in this post. Next we will learn how to generate blocks in chunks and will begin to optimise these chunks. We will also cover how to texture different block and different faces with different textures using our atlas.

Leave a Reply

Your email address will not be published. Required fields are marked *