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

PartWhat it is
VerticesA list of corner points. Each one has a number (index) starting from 0.
TrianglesA list of indices, three per triangle. 0, 1, 2 means "join corners 0, 1 and 2".
UVsWhere each corner lands on the texture, from (0,0) to (1,1).
NormalsWhich way each corner faces. Used for lighting. Unity can calculate these for you.

A square is 4 vertices and 2 triangles:

01230, 1, 20, 2, 3
Corners are listed clockwise (as seen from the front) so the triangles face you.

Example: a grid of tiles

  1. Create a material (URP: Universal Render Pipeline/Lit).
  2. Add this script to an empty object and assign the material.
  3. 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.

Build mesh onceMatrix for each tileRenderMesh every frame
One mesh, 25 placements, zero extra GameObjects.

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

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

See also