Direct answer
For a scene with identical geometry and material where objects are frequently added and removed but spread across a vast coordinate space, object pooling with individual Meshes is usually more efficient when the visible density is low and instances are spatially sparse. InstancedMesh is more efficient when the uniform subset is dense enough that draw call and CPU submission overhead dominates the cost of rendering off-screen instances.
The exact density threshold where individual draw calls outweigh the lack of per-instance culling in InstancedMesh is device and scene dependent and must be profiled. There is no universal number. It is determined by the point where visible pooled draw calls + per-object matrix updates exceed the cost of one InstancedMesh draw call plus the GPU work for instances that fall outside the view frustum.
Confirmed facts
- InstancedMesh renders many copies of one geometry and material in a single draw call using a per-instance transformation buffer. This reduces CPU submission overhead compared to many individual Mesh objects.
- InstancedMesh requires identical geometry and, for standard usage, the same material for all instances. Per-instance color and transform can be driven via instanceMatrix and optional instanceColor. Per-instance material is not supported.
- InstancedMesh frustum culling operates on the bounding box of the whole mesh, not per instance. The whole mesh is culled or rendered.
- Object pooling reuses a fixed set of Mesh instances to avoid allocation and garbage collection churn when instances appear and disappear.
- Pooled Meshes allow individual frustum culling, unique materials per object, individual shadows, and per-object raycast picking.
- Pooled Meshes scale draw calls with visible objects and incur per-object matrix and scene graph updates.
Likely explanation for this case
With frequent add/remove and a vast coordinate space, the visible fraction of instances is typically small. InstancedMesh will still submit the whole instance buffer and the GPU will process instances that are off-screen because culling is per-mesh. That wasted GPU work can outweigh the draw call savings.
Pooling keeps draw calls proportional to visible objects only, so sparse distributions benefit from per-instance frustum culling. The cost is higher CPU submission and more draw calls as density rises.
For uniform geometry and material, a hybrid is common: InstancedMesh for dense clusters where most instances are visible, pooled Meshes for sparse or non-uniform items.
Steps needed for this case
- Profile baseline: log
renderer.info.render.calls and frame time with your current pooled setup at target instance count.
- Isolate the uniform subset that shares exactly the same geometry and material. Build a prototype with InstancedMesh for that subset only.
- Measure buffer update cost for dynamic transforms. Update
instanceMatrix and set needsUpdate only when data changes, and consider partial writes for sparse updates.
- Compare draw call count, GPU frame time, and visible fraction. If visible fraction is low and spread, keep pooling. If visible fraction is high and clustered, InstancedMesh wins.
- Verify functional constraints by testing per-instance material change and individual raycast hit on InstancedMesh versus pooled Meshes.
One missing diagnostic that changes the recommendation: Do all dynamic instances share exactly the same geometry and material, and do you require per-instance material variation or individual raycast picking per object? If material variation or per-object picking is required, InstancedMesh is not applicable for those instances.