This is the second blogpost in a planned series dealing with procedurally generated terrain in Unity. I am gearing up to a project that will use terrain in a reactive and interactive way. In the first tutorial we talked about a single face containing 3 vertices and a triangle (with the appropriate winding). Today we will look at creating a larger, uniform mesh.
Mesh Review
Let’s have a quick look at “Unity mesh generation: vertices, triangles, winding” to remind ourselves about meshes. A mesh is made up of vertices and triangles. Triangles need to be wound CLOCKWISE. If we fill the appropriate data structures with this information we can draw a mesh (don’t forget about your normals, and how to “upload” the data to your mesh).
Improving the Mesh
Let’s copy the SingleFace GameObject and C# script from last time and rename it to “SimpleMesh”. We will remove a number of things and add two public variables for the x size and z size:
I also added a xVertices and zVertices to hold the number of vertices each dimension will hold. Here is a quick illustration of the why we need to add 1 to the x and z sizes of our mesh to get the correct vertex count:
Since we are defining the x/z size as the number of squares in each dimension, and each square has 2 vertices per dimension we need to account for this in our code, hence:
xVertices = xSize + 1;
zVertices = zSize + 1;
Moving right along…
Let’s talk about allocating our data structures Vector3[] vertices
and int[] triangles
:
vertices = new Vector3[xVertices * zVertices];
triangles = new int[xSize * zSize * 6];
Before we can fill the arrays with data, we need to allocate their size (we could use a different data type to allocate space as we need it, but for now we will stick with arrays).xVertices * zVertices
gives us the total number of vertices we have to calculate.
The size of the triangles array might need a little bit of explaining. We know from last time that each triangle needs 3 vertices (added in clockwise order to create a proper mesh). We would also like to have a mesh without holes (for the time being), which means that we want to create squares. So by using two triangles, one rotated so that the hypotenuses form the diagonal of a square, we need 2 * 3
vertices to have a seamless surface.
Fillers…
Let’s now move right along and fill our vertices
array.
To do this we need to loop over both dimensions (x and z) with a nested for-loop. Then we simply assign the indices from each to the appropriate dimensions like so:
Vector3 tempVert = Vector3.zero;
for (int z = 0, i = 0; z < zVertices; z++)
{
for (int x = 0; x < xVertices; x++)
{
tempVert.x = x;
tempVert.z = z;
vertices[i] = tempVert;
i++;
}
}
Notice how I added the int i = 0
variable into the z-loop. We could also move the i++
into the x-loop declaration like this and remove it from the body of the code:for (int x = 0; x < xVertices; x++, i++)
I prefer to keep the loop declarations clean and add any additional counters manually inside the body of the code. But feel free to experiment. Word of caution: be careful with some of the variables we will add further down…
Visualizing Vertices
At this point it would be nice if we could see that your loops created the vertices as we intended to without having to deal with the triangles quite yet. For this we can use a nice little helper function Unity has built in.
private void OnDrawGizmos()
This function is very useful for adding additional information to a scene (not your game view!!) for debugging purposes. Using OnDrawGizmos()
we can draw the vertices and make sure everything works as expected before moving on:
private void OnDrawGizmos()
{
if (vertices == null)
{
return;
}
for (int i = 0; i < vertices.Length; i++)
{
Gizmos.color = Color.red;
Gizmos.DrawSphere(vertices[i], 0.0666f);
}
}
Slow it down… Coroutines
This seems to be working fine. Let’s add one more trick to see the order in which the vertices are created just to solidify the concept of vertices. For this we are creating a coroutine.
Typically, when a function is called the remainder of the code “pauses” until the function is done with its job and returns. With a coroutine, we can call a function and pause it, give control back to Unity and resume the original function in the future! This is handy for creating timers, or for carrying out heavy computations less often than at frame rate (imagine tracking a horde of enemies at frame rate!).
A coroutine needs 3 separate elements to work:
- A function of type
IEnumerator
- A “yield return” inside the
IEnumerator
function - A
StartCoroutine()
function calling theIEnumerator
function
What does this mean for us specifically?
1.
IEnumerator MakeMesh()
{
}
The function type IEnumerator
tells Unity that we want to interrupt what happens in this function and get back to it later…
2.
IEnumerator MakeMesh()
{
yield return new WaitForSeconds(0.15f);
}
yield return
is where the MakeMesh()
function will yield, and return functionality back to the rest of the script. WaitForSeconds()
then instructs Unity to come back to MakeMesh()
after the indicated time. Then MakeMesh()
yields and returns again for some time, and so on, and so forth…
Coding the Coroutine
Adding our vertex generating code we will have something like:
IEnumerator MakeMesh()
{
xVertices = xSize + 1;
zVertices = zSize + 1;
vertices = new Vector3[xVertices * zVertices];
triangles = new int[xSize * zSize * 6];
Vector3 tempVert = Vector3.zero;
for (int z = 0, i = 0; z < zVertices; z++)
{
for (int x = 0; x < xVertices; x++)
{
tempVert.x = x;
tempVert.z = z;
vertices[i] = tempVert;
i++;
yield return new WaitForSeconds(0.15f);
}
}
}
Now just add StartCoroutine(MakeMesh());
in void Start()
above and we have a nice visualization of the order in which our vertices are created (don’t forget to switch to Scene View when running the Unity Project!). Hopefully you see the great potential and usefulness of coroutines at this point.
Fillers… 6.0
Now that the vertices are taken care of, let’s have a look at the triangles. We already have a for-loop structure that creates all our vertices for us. This means we know the order of vertex generation and we should be able to create our triangles inside this loop at the same time as our vertices.
From the previous tutorial we know that we need clockwise winding. We know we are creating vertices from let to right, bottom to top. We know the size of our vertices array and therefore the number of vertices we have to assign to the triangles… All we need to figure out is how to properly fill this array.
Let’s look at the square example from above one more time and try to find an algorithm for creating our winding. Let’s pretend we are inside a for-loop going through all the vertices:
There is a lot happening here, so let’s break it down.
- Define vertex as 0 (index into vertices array!!)
- We know the location of the BL vertex (index 0 of array vertices)
- This means we can write the BL as “vertex + 0”
- To go UP to TL we first need to go past ALL vertices in the first row; so if we add
vertex + xSize
we in fact tell the program to jump to the very last vertex of the current row.vertex + xSize + 1
then gets us UP one row! This is a bit complicated, so make sure this makes sense to you. Look back at the animation of the order in which the vertices are created. To get to any vertex UP 1 row from the current vertex we need to traverse ALL vertices in between. Therefore,xSize + 1
is equal toz + 1
!!! - To get to BR we go
vertex + 1
I leave it as a mental exercise for you to verify that triangle 2 is also done correctly in the example above…
One thing is still missing and that is where does the vertex
variable come from. We need an int vert = 0;
variable just outside the for-loop and then increment vert++
in every loop (both in the z AND x loop!):
IEnumerator MakeMesh()
{
xVertices = xSize + 1;
zVertices = zSize + 1;
vertices = new Vector3[xVertices * zVertices];
triangles = new int[xSize * zSize * 6];
Vector3 tempVert = Vector3.zero;
int vert = 0;
for (int z = 0, i = 0; z < zVertices; z++)
{
for (int x = 0; x < xVertices; x++)
{
tempVert.x = x;
tempVert.z = z;
vertices[i] = tempVert;
//pseudo code
/*
t1 = vert + 0;
t2 = vert + xSize + 1;
t3 = vert + 1;
t4 = vert + 1;
t5 = vert + xSize + 1;
t6 = vert + xSize + 2;
*/
i++;
vert++;
yield return new WaitForSeconds(0.15f);
}
vert++;
}
}
Triangle assignment
Ok… next we need to add the triangles to the correct indices of the triangles
array. In the pseudo code above I chose t1 through t6
. This wasn’t arbitrary! Since we are adding 6 values at each iteration of the loop (t1 through t6) we need to increment the index by 6 through each iteration of the loop (tris += 6
).
Let’s add another variable just outside the for-loop to keep track of the triangles
indices: int tris = 0;
IEnumerator MakeMesh()
{
xVertices = xSize + 1;
zVertices = zSize + 1;
vertices = new Vector3[xVertices * zVertices];
triangles = new int[xSize * zSize * 6];
Vector3 tempVert = Vector3.zero;
int vert = 0;
int tris = 0;
for (int z = 0, i = 0; z < zVertices; z++)
{
for (int x = 0; x < xVertices; x++)
{
tempVert.x = x;
tempVert.z = z;
vertices[i] = tempVert;
triangles[0 + tris] = vert + 0;
triangles[1 + tris] = vert + xSize + 1;
triangles[2 + tris] = vert + 1;
triangles[3 + tris] = vert + 1;
triangles[4 + tris] = vert + xSize + 1;
triangles[5 + tris] = vert + xSize + 2;
tris += 6;
i++;
vert++;
yield return new WaitForSeconds(0.15f);
}
vert++;
}
}
We use the same concept here as we did with the moving UP one row with the vertices. We index each triangle position from 0-5
and then add the tris
offset to it to get to the next unassigned index in the triangles
array. This of course means that our tris
variable can NOT be incremented by 1 like all our other ones but rather by tris += 6;
.
Hit play… and you will get an IndexOutOfRangeException
… If you payed very close attention to our variables and loops you might suspect why that is…
… I’ll give you a minute to check…
Recall that our loops are going through the vertices, which are defined as xSize + 1
and zSize + 1
. It is this + 1
that is responsible for the error right now. Vertices are one larger than the dimensions of the mesh. However, a FULL triangle is calculate from the leftmost vertex, which means we should only use xSize and zSize WITHOUT the + 1
. It’s an easy fix though. Simply wrap the entire triangle code in an if statement:
if (z < zSize && x < xSize)
{//face of quad!
triangles[0 + tris] = vert;
triangles[1 + tris] = vert + xSize + 1;
triangles[2 + tris] = vert + 1;
triangles[3 + tris] = vert + 1;
triangles[4 + tris] = vert + xSize + 1;
triangles[5 + tris] = vert + xSize + 2;
tris += 6;
vert++;
}
And now when we hit play we get a beautiful mesh:
That’s it for today. Don’t forget to download the project for today… Until next time…
Here is the download to the updated Unity project.
Become a Patron!