Programmatically Generating Meshes In Unity3D

This tutorial will demonstrate how to programmatically generate a mesh in Unity with C# code. This code was used in my procedural terrain/flight demo. I’m going to show how to make a triangular grid, but you can easily extrapolate this to make square grids and other shapes you need for your Unity projects.

I’m making a triangular grid because it generally looks better and is useful for making hex grids. This excellent tutorial by Red Blob Games has more details on the math involved in hex grids.

Background

Before we start, it’s important to know how Unity stores Mesh objects. Here are the basics:

  • A mesh object has a Vector3[] array of all its vertices. For this tutorial we’ll be setting the Y value to “terrain” height and the triangular grid will be spaced evenly along the X and Z axes.
  • The mesh’s faces are defined by an int[] array whose size is three times the number of faces. Each face is defined as a triangle represented by three indices into the vertex array. For example, the triangle {0,1,2} would be a triangle whose three corners are the 0th, 1st, and 2nd Vector3 objects in the vertex array. Clockwise triangles face up, while counterclockwise triangles look down (faces are usually rendered one-sided).
  • The mesh also has a Vector2[] array of UVs whose size is the same as the number of vertices. Each Vector2 corresponds to the texture offset for the vertex of the same index. UVs are necessary for a valid mesh, but if you’re not texturing then you can pass in “new Vector2(0f,0f)” for everything. If you want a simple texture projection on the mesh then pass in your X and Z coordinates instead.
  • When you are done making changes to a Mesh object you will need to call RecalculateBounds() so that the camera won’t mistakenly ignore it. You should probably call RecalculateNormals() too.

To make a valid Mesh object we need to generate all three of these data sets.

Scripting

Let’s start by attaching a new script to a new GameObject. We’ll use some basic variables for the dimensions and spacing of the grid. Our GetHeight() function will be used to generate terrain heights across the triangular grid, but terrain generation is outside the scope of this tutorial.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ProceduralTerrain : MonoBehaviour {

    public int width = 10;
    public float spacing = 1f;
    public float maxHeight = 3f;
    public MeshFilter terrainMesh = null;

    void Start()
    {
        ; // TODO
    }

    // Return the terrain height at the given coordinates.
    // TODO Currently it only makes a single peak of max_height at the center,
    // we should replace it with something fancy like multi-layered perlin noise sampling.
    float GetHeight(float x_coor, float z_coor)
    {
        float y_coor =
            Mathf.Min(
                0,
                max_height - Vector2.Distance(Vector2.zero, new Vector2(x_coor, z_coor)
            )
        );
        return y_coor;
    }
}

I’ve added MeshFilter and MeshRenderer components to the ProceduralTerrain object, and assigned the object to its own Terrain Mesh field.

Now we’re going to generate the point grid and a single triangle in its corner. Most of these points will be unused, we’ll use them in the next step.

    void Start()
    {
        if (terrainMesh == null)
        {
            Debug.LogError("ProceduralTerrain requires its target terrainMesh to be assigned.");
        }

        GenerateMesh();
    }

    void GenerateMesh ()
    {
        float start_time = Time.time;

        List<Vector3[]> verts = new List<Vector3[]>();
        List<int> tris = new List<int>();
        List<Vector2> uvs = new List<Vector2>();

        // Generate everything.
        for (int z = 0; z < width; z++)
        {
            verts.Add(new Vector3[width]);
            for (int x = 0; x < width; x++)
            {
                Vector3 current_point = new Vector3();
                current_point.x = x * spacing;
                current_point.z = z * spacing; // TODO this makes right triangles, fix it to be equilateral

                current_point.y = GetHeight(current_point.x, current_point.z);

                verts[z][x] = current_point;
                uvs.Add(new Vector2(x,z)); // TODO Add a variable to scale UVs.
            }
        }

        // Only generate one triangle.
        // TODO Generate a grid of triangles.
        tris.Add(0);
        tris.Add(1);
        tris.Add(width);

        // Unfold the 2d array of verticies into a 1d array.
        Vector3[] unfolded_verts = new Vector3[width*width];
        int i = 0;
        foreach (Vector3[] v in verts)
        {
            v.CopyTo(unfolded_verts, i * width);
            i++;
        }

        // Generate the mesh object.
        Mesh ret = new Mesh();
        ret.vertices = unfolded_verts;
        ret.triangles = tris.ToArray();
        ret.uv = uvs.ToArray();

        // Assign the mesh object and update it.
        ret.RecalculateBounds();
        ret.RecalculateNormals();
        terrainMesh.mesh = ret;

        float diff = Time.time - start_time;
        Debug.Log("ProceduralTerrain was generated in " + diff + " seconds.");
    }

When we play the scene, we get this triangle.

It’s magenta because no material has been assigned yet.

There are several problems here, but we’ll address them one at a time so that you can see what’s going on. For now, let’s generate one triangle per vertex. We can do that in our for() loop as we generate the vertices.

        // Generate everything.
        for (int z = 0; z < width; z++)
        {
            verts.Add(new Vector3[width]);
            for (int x = 0; x < width; x++)
            {
                Vector3 current_point = new Vector3();
                current_point.x = (x * spacing);
                current_point.z = z * spacing;

                current_point.y = GetHeight(current_point.x, current_point.z);

                verts[z][x] = current_point;
                uvs.Add(new Vector2(x,z)); // TODO Add a variable to scale UVs.

                // TODO The edges of the grid aren't right here, but as long as we're not wrapping back and making underside faces it should be okay.

                // Don't generate a triangle if it would be out of bounds.
                if (x-1 <= 0 || z <= 0 || x >= width)
                {
                    continue;
                }
                // Generate the triangle north of you.
                tris.Add(x + z*width);
                tris.Add(x + (z-1)*width);
                tris.Add((x-1) + (z-1)*width);

                // TODO Generate the triangle northwest of you.
            }
        }

I’ll also apply a new material to the ProceduralTerrain object. Restarting the scene generates this mesh:

And now you know how to make Final Fantasy XIII: Lightning Returns. Try it for your next game jam!

Now we’re getting somewhere! Currently we’re generating a square grid, and if we added another triangle per point we would have a grid of square quads composed of right triangles. But we want a triangular grid. Next we’ll offset every even-numbered row so that the mesh is triangular instead of two-poly quads.

        // Generate everything.
        for (int z = 0; z < width; z++)
        {
            verts.Add(new Vector3[width]);
            for (int x = 0; x < width; x++)
            {
                Vector3 current_point = new Vector3();
                current_point.x = (x * spacing);
                current_point.z = z * spacing; // TODO this makes right triangles, fix it to be equilateral
                // Triangular grid offset
                int offset = z % 2;
                if (offset == 1)
                {
                    current_point.x -= spacing * 0.5f;
                }

                current_point.y = GetHeight(current_point.x, current_point.z);

                verts[z][x] = current_point;
                uvs.Add(new Vector2(x,z)); // TODO Add a variable to scale UVs.

                // Don't generate a triangle if it would be out of bounds.
                int current_x = x + (1-offset);
                if (current_x-1 <= 0 || z <= 0 || current_x >= width)
                {
                    continue;
                }
                // Generate the triangle north of you.
                tris.Add(x + z*width);
                tris.Add(current_x + (z-1)*width);
                tris.Add((current_x-1) + (z-1)*width);

                // TODO Generate the triangle northwest of you.
            }
        }

This generates a much more regular mesh:

The dark edges in this screenshot are caused by a bug that made faces only visible from the underside. This is because the last triangle in every other row was wrapping around to connect to the next row.

Next we’ll fix the holes in the mesh. A triangular grid has twice as many faces as vertices. We only have to make one more face per vertex to do this.

        // Generate everything.
        for (int z = 0; z < width; z++)
        {
            verts.Add(new Vector3[width]);
            for (int x = 0; x < width; x++)
            {
                Vector3 current_point = new Vector3();
                current_point.x = (x * spacing);
                current_point.z = z * spacing; // TODO this makes right triangles, fix it to be equilateral
                // Triangular grid offset
                int offset = z % 2;
                if (offset == 1)
                {
                    current_point.x -= spacing * 0.5f;
                }

                current_point.y = GetHeight(current_point.x, current_point.z);

                verts[z][x] = current_point;
                uvs.Add(new Vector2(x,z)); // TODO Add a variable to scale UVs.

                // TODO The edges of the grid aren't right here, but as long as we're not wrapping back and making underside faces it should be okay.

                // Don't generate a triangle if it would be out of bounds.
                int current_x = x + (1-offset);
                if (current_x-1 <= 0 || z <= 0 || current_x >= width)
                {
                    continue;
                }
                // Generate the triangle north of you.
                tris.Add(x + z*width);
                tris.Add(current_x + (z-1)*width);
                tris.Add((current_x-1) + (z-1)*width);

                // Generate the triangle northwest of you.
                if (x-1 <= 0 || z <= 0)
                {
                    continue;
                }
                tris.Add(x + z*width);
                tris.Add((current_x-1) + (z-1)*width);
                tris.Add((x-1) + z*width);
            }
        }

Finally, let’s center the mesh on the peak. We’ll subtract half the width from the X and Z coordinates of each vertex.

                Vector3 current_point = new Vector3();
                current_point.x = (x- * spacing) - width/2f;
                current_point.z = z * spacing - width/2f; // TODO this makes right triangles, fix it to be equilateral

I reduced the Max Height value from the default here.

That should cover all the basics of programmatically generating a mesh in code. Click here to download the complete source code.

8 thoughts on “Programmatically Generating Meshes In Unity3D

  1. fahd

    I want the user to add points to the screen and the application will automatic connect them together to create one mesh from these points on runtime.

        1. Thomar Post author

          Sounds like a common question. :D

          You would want a manager that listens for mouse clicks (Input.GetMouseButtonDown()) in its Update() loop and stores a list of the vertices. Each time a vertex is added you regenerate the mesh. However, the manager needs to determine how to hook the vertices together, which is a fun UI exercise and there are lots of different ways you could do it depending on the function of your application.

  2. Tim

    Hi Thomar,
    I am currently facing the bug where the ends of my triangles are showing up black. I’ve searched forums everywhere but have been unable to find an answer, if you could help that would be great!

    I was also wondering if you taught paid lessons over Skype? You have my email if you want to talk about it.

    Thanks,
    Tim

    1. Thomar Post author

      Check the backside, that would happen if you were getting faces stretched across the rows because of an off-by-one error.

  3. Mr.Pavlov

    Hi dear, this is a great post, thank you!
    As the same question above, I want the user to add array of points which can import from the text file to the screen and the application will automatic connect them together to create one mesh from these points on runtime.
    Plz Thomar, can you send to me your source code! Thank you very much!

Comments are closed.