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
```gdscript
# 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:
```gdscript
# 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.
```gdscript
# 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:
```gdscript
# 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) → `MeshInstance3D` with `CollisionShape3D`
- Background scatter (small rocks, pebbles, shrubs, broken crates) → `MultiMeshInstance3D`, no collision
- Mid-ground props (fence sections, lamp posts) → `MultiMeshInstance3D` with 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.*