Procedural Grid

2022-12-30

Create a grid of points.

Use a coroutine to analyze their placement.

Define a surface with triangles.

Automatically generate normals.

Add texture coordinates and tangents.

*Learning Notes from Catlike Coding Tutorials

Creating the Mesh

  private Mesh mesh;

	private IEnumerator Generate () {
		WaitForSeconds wait = new WaitForSeconds(0.05f);
		
		GetComponent<MeshFilter>().mesh = mesh = new Mesh(); // Define Mesh
		mesh.name = "Procedural Grid";

		vertices = new Vector3[(xSize + 1) * (ySize + 1)];
		for (int i = 0, y = 0; y <= ySize; y++) {
			for (int x = 0; x <= xSize; x++, i++) {
				vertices[i] = new Vector3(x, y);
				yield return wait;
			}
		}
		mesh.vertices = vertices; // Define Mesh Vertices
	}

Which side a triangle is visible from is determined by the orientation of its vertex indices. By default, if they are arranged in a clockwise direction the triangle is considered to be forward-facing and visible. Counter-clockwise triangles are discarded so we don’t need to spend time rendering the insides of objects, which are typically not meant to be seen anyway.

The two sides of a triangle

  private IEnumerator Generate () {
		...

		int[] triangles = new int[6];
		triangles[0] = 0;
		triangles[3] = triangles[2] = 1;
		triangles[4] = triangles[1] = xSize + 1;
		triangles[5] = xSize + 2;
		mesh.triangles = triangles; // Define Mesh Triangles
	}

A quad made with two triangles

Mesh Code

  private void Awake () {
		Generate();
	}

	private void Generate () {
		GetComponent<MeshFilter>().mesh = mesh = new Mesh();
		mesh.name = "Procedural Grid";

		vertices = new Vector3[(xSize + 1) * (ySize + 1)];
		for (int i = 0, y = 0; y <= ySize; y++) {
			for (int x = 0; x <= xSize; x++, i++) {
				vertices[i] = new Vector3(x, y);
			}
		}
		mesh.vertices = vertices;

		int[] triangles = new int[xSize * ySize * 6];
		for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++) {
			for (int x = 0; x < xSize; x++, ti += 6, vi++) {
				triangles[ti] = vi;
				triangles[ti + 3] = triangles[ti + 2] = vi + 1;
				triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
				triangles[ti + 5] = vi + xSize + 2;
			}
		}
		mesh.triangles = triangles;
	}

Generating Additonal Vertex Data

Normals

Normals are defined per vertex. The default normal direction is (0, 0, 1). The Mesh.RecalculateNormals method computes the normal of each vertex by figuring out which triangles connect with that vertex, determining the normals of those flat triangles, averaging them, and normalizing the result.

  mesh.RecalculateNormals(); // Define Mesh Normals

Texture

UV Coordinates

  Vector2[] uv = new Vector2[vertices.Length];
  ...
    uv[i] = new Vector2((float)x / xSize, (float)y / ySize); // use float
  ...
  mesh.uv = uv; // Define Mesh UV

Tiling Settings

Correct UV coordinates, tiling 1,1 vs. 2,1

The texture’s wrap mode: clamp or repeat. By settings tiling to (2, 1) the U coordinates will be doubled. If the texture is set to repeat, then we’ll see two square tiles of it.

Normal Maps & Tagent Vectors

Normal maps are defined in tangent space. This approach allows us to apply the same normal map in different places and orientations.

The cross product of them yields the third direction needed to define 3D space. So a tangent is a 3D vector, but Unity actually uses a 4D vector. Its fourth component is always either −1 or 1, which is used to control the direction of the third tangent space dimension – either forward or backward. This facilitates mirroring of normal maps, which is often used in 3D models of things with bilateral symmetry, like people. The way Unity’s shaders perform this calculation requires us to use −1.

  Vector4[] tangents = new Vector4[vertices.Length];
	Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
  ...
    tangents[i] = tangent;
  ...
  mesh.tangents = tangents; // Define Mesh Tagents

Tagent Space