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.