Rendering thousands of rocks, trees, or crates efficiently is one of the most common performance challenges in open-world Godot 4 projects — and the answer lies in understanding exactly when to use MeshInstance3D versus MultiMeshInstance3D. Both nodes render a mesh, but they scale in completely different ways under load.
Godot 4 issues one draw call per MeshInstance3D node by default. Place 500 identical rocks as individual MeshInstance3D nodes and you're looking at 500 draw calls per frame — before shadow passes, before LODs, before anything else. MultiMeshInstance3D, on the other hand, sends all instances of the same mesh to the GPU in a single draw call, no matter how many copies you place. For repeated props (foliage, debris, fence posts, tile floor pieces), that difference can drop your GPU time by 80% or more.
When MeshInstance3D is the right choice
![]()
MeshInstance3D is Godot 4's default mesh renderer and the right tool for unique or infrequently repeated props. Use it when:
- Each instance needs its own
AnimationPlayer,AnimationTree, or physics body - The prop appears fewer than ~20 times in the scene
- You need per-instance material overrides that differ significantly between copies
- The mesh must be individually selectable or interactable at runtime
# Standard MeshInstance3D placement
var rock = preload("res://assets/rock_01.glb").instantiate()
rock.position = Vector3(10, 0, 5)
add_child(rock)
MeshInstance3D integrates cleanly with Godot's scene tree. Each node can have its own CollisionShape3D, Area3D, visibility flags, or script. The cost is one draw call per node, so keep instance counts low.
Surface override materials
You can assign per-surface materials directly on MeshInstance3D without touching the original mesh resource — useful when the same GLB prop needs a damaged, snowy, or emissive variant at specific scene locations:
# Override surface 0 material on a specific instance
var mesh_inst = $Props/Rock_Damaged
mesh_inst.set_surface_override_material(0, damaged_mat)
This works per-node, which is exactly what makes MeshInstance3D inflexible at scale: every override is stored individually in the scene tree.
When MultiMeshInstance3D cuts your draw calls
MultiMeshInstance3D is the right tool when you're placing the same mesh repeatedly and don't need per-instance behavior. Foliage, rocks along a riverbank, streetlights, fence posts, scattered debris — any prop repeated 50+ times belongs here.
Internally, Godot batches all instances into a single GPU buffer and renders them with one draw call. The CPU overhead of managing 5,000 rocks drops to near-zero at runtime.
# Set up MultiMeshInstance3D at runtime
var mmi = MultiMeshInstance3D.new()
var mm = MultiMesh.new()
mm.mesh = preload("res://assets/rock_01.glb").meshes[0]
mm.instance_count = 500
mm.transform_format = MultiMesh.TRANSFORM_3D
# Assign transforms in a loop
for i in 500:
var t = Transform3D()
t.origin = Vector3(randf_range(-100, 100), 0, randf_range(-100, 100))
mm.set_instance_transform(i, t)
mmi.multimesh = mm
add_child(mmi)
MultiMesh also supports per-instance custom data (a Color value passed to the shader as INSTANCE_CUSTOM), letting you drive variation like wind phase offset, tint randomisation, or damage state through a shader without extra draw calls:
# Pass per-instance wind phase as Color.r
for i in mm.instance_count:
mm.set_instance_custom_data(i, Color(randf(), 0, 0, 0))
In your shader, access it with INSTANCE_CUSTOM.r.
Choosing between them: a decision checklist
![]()
Use this checklist before placing any repeated prop:
| Condition | Use |
|---|---|
| Unique mesh or fewer than 20 instances | MeshInstance3D |
| 50+ identical instances | MultiMeshInstance3D |
| Per-instance physics / colliders | MeshInstance3D |
| Per-instance animation | MeshInstance3D |
| Foliage, rocks, debris scatter | MultiMeshInstance3D |
| Needs individual runtime removal | MeshInstance3D |
| Wind/shader variation via color data | MultiMeshInstance3D |
| Static environment props (repeated) | MultiMeshInstance3D |
For the middle range (20–50 instances), profile both. Godot 4's Vulkan renderer reduces the per-draw-call overhead compared to GLES3, so the crossover point varies by target hardware.
Collision with MultiMesh
MultiMeshInstance3D has no built-in collision. For scattered props that need physics interaction, use a StaticBody3D with a trimesh or convex CollisionShape3D placed at matching world positions — separate from the visual MultiMesh. For purely decorative scatter (grass, distant rocks), skip collision entirely and save the CPU budget.
Practical mesh instancing in Godot 4 with free GLB props
Free GLB assets from BitSoul's marketplace drop straight into this workflow. For a typical outdoor scene:
- Hero props (interactive barrels, unique boulders, quest items) →
MeshInstance3DwithCollisionShape3D - Background scatter (small rocks, pebbles, shrubs, broken crates) →
MultiMeshInstance3D, no collision - Mid-ground props (fence sections, lamp posts) →
MultiMeshInstance3Dwith separate static collision shapes at matching transforms
This split keeps the scene tree readable, collision counts manageable, and GPU draw calls minimal. Monitor your draw call budget in Debug → Visible Draw Calls during scene editing — if you're over 200 for static geometry in a single chunk, a MeshInstance3D-heavy scatter is the likely culprit.
For LOD layering on top of this setup, pair MultiMeshInstance3D with Godot 4's GeometryInstance3D.visibility_range_begin and visibility_range_end properties to fade props out beyond a set distance without additional scripting.
Browse BitSoul's marketplace for game-ready GLB props optimised for exactly this kind of instanced scatter — low poly counts, clean UV layouts, and single-material meshes that batch cleanly with MultiMesh.
This post is part of the Ultimate Guide to Free 3D Game Assets — BitSoul's complete reference for formats, texturing, rigging, optimization, and engine integration.
Need drop-in assets for this workflow? Grab the 70-model Food Bundle on the BitSoul marketplace and drop them straight into your project.