Quick answer: The most common cause is that the MultiMesh resource has instance_count set to 0 (the default). You must set instance_count to the number of instances you want before setting any transforms.
Here is how to fix Godot MultiMesh instance not visible. You set up a MultiMeshInstance3D to render thousands of grass blades or trees, but nothing appears in the viewport. No errors, no warnings—just an empty scene. MultiMesh rendering in Godot 4 requires several properties to be configured correctly, and the default values for most of them result in nothing being drawn. This guide walks through every reason your MultiMesh might be invisible.
The instance_count Must Be Set Before Anything Else
The single most common cause of invisible MultiMesh instances is an instance_count of zero. The default value is 0, and you must set it before assigning transforms, colors, or custom data. If you set transforms before setting the count, those transforms are silently discarded.
# BAD: setting transforms before instance_count
var mm := MultiMesh.new()
mm.mesh = preloaded_mesh
mm.set_instance_transform(0, Transform3D.IDENTITY) # Ignored!
mm.instance_count = 100 # Clears all previous data
# GOOD: set instance_count first
var mm := MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.mesh = preloaded_mesh
mm.instance_count = 100 # Allocate first
for i in mm.instance_count:
mm.set_instance_transform(i, Transform3D.IDENTITY)
Also note that changing instance_count after it has been set will reset all instance data. If you need to resize, you must re-apply all transforms afterward.
Assign a Mesh to the MultiMesh Resource
A MultiMesh without a mesh property has nothing to draw. This seems obvious, but when setting up MultiMesh via code, it is easy to forget to assign the mesh before adding it to the scene.
extends MultiMeshInstance3D
@export var grass_mesh: Mesh
func _ready() -> void:
var mm := MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.mesh = grass_mesh # Do not forget this
mm.instance_count = 1000
multimesh = mm # Assign to the MultiMeshInstance3D
# Verify mesh is set
if multimesh.mesh == null:
push_error("MultiMesh has no mesh assigned")
return
_place_instances()
If you are using the editor, check the MultiMesh resource in the inspector. Expand the MultiMeshInstance3D node, find the multimesh property, expand it, and verify that mesh is not [empty].
Set Valid Transforms for Each Instance
After setting instance_count, every instance starts with a default zero transform in some configurations, meaning all instances are at the origin with zero scale. You must explicitly set each instance’s transform.
func _place_instances() -> void:
for i in multimesh.instance_count:
var pos := Vector3(
randf_range(-50.0, 50.0),
0.0,
randf_range(-50.0, 50.0))
var basis := Basis.IDENTITY.rotated(
Vector3.UP, randf() * TAU)
var scale_factor := randf_range(0.8, 1.2)
basis = basis.scaled(Vector3.ONE * scale_factor)
var xform := Transform3D(basis, pos)
multimesh.set_instance_transform(i, xform)
A common mistake is setting the position but forgetting to include scale in the basis. Basis() with default constructor creates an identity basis (scale 1.0), which is fine. But Basis(Vector3.ZERO, Vector3.ZERO, Vector3.ZERO) creates a zero-scale basis that makes instances invisible.
Fix the Custom AABB for Frustum Culling
If your MultiMesh instances appear when the camera is close but vanish when you move the camera away or to the side, the issue is frustum culling with an incorrect AABB (Axis-Aligned Bounding Box).
Godot uses the MultiMesh’s AABB to decide whether the entire batch is visible to the camera. If the AABB is too small, Godot culls the instances even when some of them should be visible.
# Set a custom AABB that covers all instances
func _update_aabb() -> void:
var aabb := AABB()
for i in multimesh.instance_count:
var pos := multimesh.get_instance_transform(i).origin
aabb = aabb.expand(pos)
# Add padding for mesh size
aabb = aabb.grow(5.0)
multimesh.custom_aabb = aabb
# Or set a large AABB to effectively disable culling
func _disable_culling() -> void:
multimesh.custom_aabb = AABB(
Vector3(-1000, -1000, -1000),
Vector3(2000, 2000, 2000))
Setting a very large AABB means Godot will never cull the MultiMesh, which is fine for small instance counts. For large scenes with thousands of instances spread across the map, calculate a proper AABB to maintain good performance.
MultiMeshInstance2D Specific Issues
For 2D games using MultiMeshInstance2D, the setup is slightly different. The transform format must be TRANSFORM_2D, and you use set_instance_transform_2d() instead:
extends MultiMeshInstance2D
@export var star_texture: Texture2D
func _ready() -> void:
var mm := MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_2D
# For 2D, assign a QuadMesh or use the texture property
var quad := QuadMesh.new()
quad.size = Vector2(16, 16)
mm.mesh = quad
mm.instance_count = 500
multimesh = mm
texture = star_texture
for i in mm.instance_count:
var pos := Vector2(
randf_range(0, 1920),
randf_range(0, 1080))
var xform := Transform2D(0.0, pos)
mm.set_instance_transform_2d(i, xform)
A common 2D-specific mistake is using TRANSFORM_3D with set_instance_transform_2d(). The transform format and the setter method must match, or the data is interpreted incorrectly and instances appear at wrong positions or not at all.
Debugging Checklist
When your MultiMeshInstance is not visible, check these in order:
1. instance_count > 0? Print multimesh.instance_count to verify. A count of zero means nothing renders.
2. Mesh assigned? Check multimesh.mesh != null. No mesh means nothing to draw.
3. Transforms set? Print multimesh.get_instance_transform(0) for the first instance. If it is a zero transform, the instance is at the origin with zero scale.
4. Correct transform format? Ensure transform_format matches your use of set_instance_transform() vs set_instance_transform_2d().
5. Material and visibility? Check that the mesh has a material with a visible shader. Also check that the MultiMeshInstance node itself is visible and not hidden by a parent node.
6. AABB covers all instances? If instances disappear when the camera moves, set a larger custom_aabb.
“MultiMesh is the single best optimization for rendering thousands of identical objects in Godot. But it has the most unintuitive setup of any rendering feature—every property defaults to a state that draws nothing.”
Related Issues
If you are dealing with performance issues beyond MultiMesh, see Fix: Godot GPUParticles Not Rendering on Mobile Devices for particle optimization. To let your QA team report rendering bugs with screenshots automatically, check out How to Add a Bug Reporter to a Godot Game.
The order of operations matters with MultiMesh: set format, set mesh, set count, then set transforms. Get any of these out of order and you get silence instead of instances.