Procedural Meshes without GameObjects
Build a shape in code from points and triangles, then draw many copies of it without making a GameObject for each one.
Think of a rubber stamp. The mesh is the stamp's shape, the material is the ink, and a matrix says where to press it. You make one stamp and press it 25 times, instead of carving 25 separate stamps.
What a mesh is made of
| Part | What it is |
|---|---|
| Vertices | A list of corner points. Each one has a number (index) starting from 0. |
| Triangles | A list of indices, three per triangle. 0, 1, 2 means "join corners 0, 1 and 2". |
| UVs | Where each corner lands on the texture, from (0,0) to (1,1). |
| Normals | Which way each corner faces. Used for lighting. Unity can calculate these for you. |
A square is 4 vertices and 2 triangles:
Example: a grid of tiles
- Create a material (URP: Universal Render Pipeline/Lit).
- Add this script to an empty object and assign the material.
- Point a camera down at the origin and press Play: you'll see a 5 × 5 grid of tiles.
using UnityEngine;
public class TileGrid : MonoBehaviour
{
[SerializeField] private Material material;
[SerializeField] private float spacing = 1.2f;
private Mesh mesh;
private RenderParams renderParams;
void OnEnable()
{
// Build one flat square, 1 × 1, lying on the ground
mesh = new Mesh { name = "Tile" };
mesh.vertices = new[] {
new Vector3(-0.5f, 0, -0.5f), // 0
new Vector3(-0.5f, 0, 0.5f), // 1
new Vector3( 0.5f, 0, 0.5f), // 2
new Vector3( 0.5f, 0, -0.5f), // 3
};
mesh.uv = new[] { Vector2.zero, Vector2.up, Vector2.one, Vector2.right };
mesh.triangles = new[] { 0, 1, 2, 0, 2, 3 };
mesh.RecalculateNormals();
mesh.RecalculateBounds();
renderParams = new RenderParams(material);
}
void Update()
{
// Drawing only lasts one frame, so we draw every frame
for (int z = -2; z <= 2; z++)
for (int x = -2; x <= 2; x++)
{
Vector3 pos = new Vector3(x * spacing, 0, z * spacing);
Matrix4x4 placement = Matrix4x4.TRS(pos, Quaternion.identity, Vector3.one);
Graphics.RenderMesh(renderParams, mesh, 0, placement);
}
}
void OnDisable()
{
Destroy(mesh); // meshes made in code aren't cleaned up automatically
}
}
Try it: change spacing to 2 in the Inspector. The tiles spread out but keep their shape, because you changed placement, not vertices.
Drawing is not the same as existing
These tiles are just pictures. They have no collider, you can't click them in the Hierarchy, and physics can't hit them. If you need gameplay, keep your own list of positions and use physics queries or a few real colliders.
Changing the mesh later
- After moving vertices, call
RecalculateNormals()andRecalculateBounds(). Wrong bounds make the mesh vanish near the screen edge. - If you'll edit it often, call
mesh.MarkDynamic()once. - Over 65,535 vertices? Set
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32before assigning triangles.
Going further: drawing thousands
The example still makes 25 separate draw calls. For hundreds or thousands of copies, use Graphics.RenderMeshInstanced, which draws them all in one go. See GPU Instancing & Batching.
Common Mistakes
- Calling
RenderMeshonce inStartand expecting it to stay. It must be called every frame. - Listing corners anticlockwise: the triangle faces away and looks invisible.
- Creating a
new Mesh()every frame. Build it once and reuse it. - Forgetting to
Destroymeshes you created, which leaks memory.
See also
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official documentation.