Quick answer: Set multimesh.instance_count = N before writing transforms at index >0. Verify the MultiMeshInstance3D uses the same MultiMesh resource you’re editing.

You generate 1000 grass instances via MultiMesh.set_instance_transform. The viewport shows one grass at origin. Other 999 set_instance_transform calls ran but had no effect.

The instance_count Gate

MultiMesh allocates buffers based on instance_count. Writing to an index ≥ instance_count silently no-ops. Default count is 0; without explicit setting, every write is dropped.

The Fix

@onready var mm: MultiMesh = $MultiMeshInstance3D.multimesh

func _ready():
    mm.instance_count = 1000   # allocate buffer FIRST
    for i in 1000:
        var t = Transform3D()
        t.origin = Vector3(randf_range(-50, 50), 0, randf_range(-50, 50))
        mm.set_instance_transform(i, t)

Buffer first, transforms second. Now all 1000 instances appear.

Custom Data and Color

If you want per-instance color/data, set the format flags before changing count:

mm.use_colors = true
mm.use_custom_data = true
mm.instance_count = 1000
mm.set_instance_color(i, Color.RED)
mm.set_instance_custom_data(i, Color(randf(), randf(), randf(), randf()))

The flags control whether the buffer has space for those per-instance attributes. Toggling after instance_count is set can clear the buffer.

visible_instance_count for Streaming

mm.instance_count = 10000   # max possible
mm.visible_instance_count = 500   # actually render

Allocate once at startup; vary visible_instance_count per frame to add/remove rendered grass. Buffer not reallocated; cheap.

Editor vs Runtime

Editing transforms in a @tool script during edit mode doesn’t persist unless you mark the MultiMesh as modified (Resource.emit_changed). For pure runtime usage, no issue; for procedural editor tools, call multimesh.emit_changed() after writes.

Verifying

Print multimesh.instance_count immediately before your write loop. If it’s 0 or less than the indices you write, that’s your bug. Set it to your target N first.

“instance_count is the allocation. Writes outside [0, count) silently drop. Set count first, write transforms second.”

For large procedural scatters (grass, rocks, debris), MultiMesh is dramatically faster than spawning individual nodes — one draw call for everything.