[UE5] Placing Many Copies of One Thing: Cutting Draw Calls with Instanced Static Meshes

Created: 2026-07-25

You placed 3,000 trees and stat unit's Draw went up. Why one mesh means one draw command, and how Instanced Static Meshes collapse that into one. Choosing between ISM, HISM and Foliage, index management with Add Instance, how this relates to Nanite, and a hands-on that converts 3,000 actors and compares Draw.

You lined 3,000 trees across a field. It looks great — but stat unit shows Draw climbing. Is cutting trees the only option?

There's a way that doesn't require it. When you place many copies of one mesh, collapse the draw commands into one — that's instancing. This article covers why Draw grows, how to choose between ISM, HISM and Foliage, how to drive it from Blueprint, and the question everyone asks: do I need this if I'm using Nanite?

A soft blue clay figure comparing 3,000 trees drawn individually against the same trees drawn in one command

What You'll Learn

  • One mesh = one draw command (why Draw climbs)
  • Instancing means sending only an array of positions
  • Choosing between ISM / HISM / Foliage (when in doubt, ISM)
  • Distance thinning means entering Cull Distance yourself
  • Add Instance and the index-management gotcha
  • Whether you need this with Nanite
  • Hands-on: convert 3,000 actors to ISM and compare Draw

Sponsored

Why Draw Climbs

The Draw figure in stat unit (→ the stat commands) is time spent by the CPU telling the GPU "draw this."

As a rough mental model: putting out many copies of one mesh as separate Actors tends to multiply the number of commands.

Placing 3,000 Static Mesh Actors sends 3,000 draw commands from CPU to GPU

3,000 trees means something close to 3,000 commands. Each is brief, but stacked that deep, Draw climbs.

"One Actor = one command" is an entry-level model, nothing more. The real number also depends on material slot count, render passes, shadows, and culling, so it won't be exactly 3,000. Take away only the intuition that more objects means more commands. We'll measure the actual number with stat rhi later.

The important part: it's the CPU that's loaded, not the GPU. Halving each tree's polygon count leaves the command count at 3,000, so Draw doesn't move.

SymptomCauseWhat helps
Draw is largeToo many commandsInstancing, culling
GPU is largeToo much to draw (polygons, pixels)LOD, resolution, cheaper shaders

If stat unit shows Draw as the outlier, this article applies. If GPU is the outlier, that belongs to LOD and culling or Nanite.


One Command for All of Them

The idea behind instancing is almost anticlimactic.

Say "draw this shape at these 3,000 places" once.

3,000 individual commands collapsing into one mesh description plus an array of 3,000 positions

You send two things:

  • The mesh description (shape and material), once
  • Position, rotation and scale, as an array of 3,000

The command count becomes one. The GPU still draws the shape 3,000 times in different places, but the CPU's work is a single command.

The condition is that everything shares one mesh and one material. Three tree species means three commands — still overwhelmingly fewer than 3,000.

Same shape, varied appearance. Rotation and scale are per-instance, so a stand of trees doesn't look mechanical. To vary color or surface per individual, Material Instances offer Per Instance Custom Data.

Sponsored

Three Options

UE gives you three routes into instancing. They're the same machinery underneath.

Three options side by side: ISM as the default to try first, HISM for thousands that never move, and the Foliage tool with HISM inside
RouteWhat it doesSuits
Instanced Static Mesh (ISM)Add positions to a ComponentThe default to try first. Things you may move, Nanite meshes
Hierarchical ISM (HISM)ISM plus a spatial hierarchyThousands that basically never move
Foliage toolPaint placement in the editor. HISM insideNatural props you want to place by hand

When in doubt, start with ISM. You'll often read "use HISM when there are many," but that's a premise from older UE. Modern ISM performs per-instance culling and LOD on the GPU, so it isn't true that "ISM draws everything forever."

HISM earns its place with thousands of things that basically never move. It holds placement as a hierarchy (a tree), letting it quickly narrow a wide area down to the visible clusters. Building that structure costs something, though, and moving instances means rebuilding it.

SituationPick
Dozens to hundreds, or things you may moveISM
Nanite-enabled meshesISM (fits naturally with how Nanite works)
Thousands of place-and-forget foliage or rocksHISM
You want to paint by hand in the editorThe Foliage tool (HISM inside)

The Foliage tool is a UI for painting HISM by hand. Same machinery, so the split is: Foliage when you want to place in the editor, your own Component when you want to add instances from Blueprint (→ Landscape Basics).

And decide which is faster by measuring. Swap with the same count from the same viewpoint and compare stat unit. That's the only correct approach in this area.


Driving It from Blueprint

In Blueprint, add an Instanced Static Mesh Component (or Hierarchical Instanced Static Mesh Component), assign one mesh, then add positions.

NodeWhat it doesReturns
Add InstanceAdds one instanceThe index it was given
Remove InstanceRemoves the given indexSuccess
Update Instance TransformMoves the given indexSuccess
Clear InstancesRemoves all

Add Instance takes a Transform (position, rotation, scale). Call it 3,000 times in a loop and you get 3,000 of them.

And here's the classic trap.

Removing an instance from the middle shifts every later index down by one, so a stored index now points at a different instance

Remove Instance on a middle instance shifts the later indices down.

  • Before: 0 1 2 3 4
  • Remove 20 1 2 3 (the old 3 becomes 2, the old 4 becomes 3)

If you stored "this tree is number 3" in a variable, number 3 now refers to a different tree.

Two ways around it:

  • Don't store indices: fetch the index at the moment you remove, and remove immediately
  • Don't remove: move it below the floor with Update Instance Transform so it "looks" removed

This is structurally the same problem as removing array elements mid-loop. Instances are an array internally, so every array caveat carries over.


When It Doesn't Help, and Nanite

There are cases instancing doesn't suit.

SituationWhy
Different materialsSeparate batches. They don't collapse into one
You want to move them individuallyMoving needs Update Instance Transform, which gets expensive in bulk
You need per-instance collision events or logicInstances aren't Actors, so they can't have Overlap or their own logic

The third matters most in practice. An instance is "a thing that gets drawn." Anything that needs per-individual behavior — a tree you shoot down, a chest you open — stays an Actor.

And now the question everyone has: what about Nanite?

Nanite-enabled meshes are already batched by Nanite, so converting them to ISM produces a much smaller Draw reduction

Nanite-enabled meshes are batched on the Nanite side. So if you're placing a lot of Nanite meshes, converting them to ISM reduces Draw far less.

That said, Nanite can't simply be enabled on every mesh (coverage has advanced across versions), and existing projects will have plenty of non-Nanite meshes.

The decision is easy: measure stat unit's Draw, convert, measure again. If it drops, it worked. If it doesn't, look at other measures (LOD and culling) for that case. Deciding by numbers rather than guesswork is the only correct approach here (→ Unreal Insights).

Sponsored

Hands-On: Convert 3,000 and Compare Draw

An open-field forest, roguelike rubble, tower defense obstacles. Placing many copies of one thing shows up in every genre. Let's actually convert and watch the numbers move.

Building the case

ItemDetails
MeshAny Static Mesh (Starter Content's SM_Rock works)
Count3,000
EnvironmentStandalone Game (not editor Play)

Start with 3,000 Actors. Placing them by hand is infeasible, so lay them out from Blueprint.

Create BP_ForestA (Actor) and on Event BeginPlay, run a For Loop (0–2999) placing Static Mesh Actors in a grid with Spawn Actor from Class.

Draw with 3,000 Actors placed, side by side with Draw after converting to ISM

① Measure the Actor version

Launch Standalone and type stat unit. Write down Draw (also check stat rhi's DrawPrimitive calls to see the command count itself).

Fix the camera position and orientation while measuring. Change what's visible and the numbers change, which ruins the comparison.

② Convert to ISM

Create a new BP_ForestB (Actor).

  1. Add an Instanced Static Mesh Component named Trees
  2. Set its Static Mesh to the same mesh as ①
  3. On Event BeginPlay, run a For Loop (0–2999)
  4. Inside the loop, build a Make Transform from the grid position and feed it to Add Instance (Target: Trees)

Only Spawn Actor from Class became Add Instancethe layout logic is identical to ①.

③ Thin out the distance (set the values yourself)

Distance thinning doesn't happen unless you enter the values. Two settings in the Component's details panel.

SettingValueMeaning
Instance Start Cull Distance4000Where they begin fading
Instance End Cull Distance6000Where they disappear entirely

Units are centimeters, so that's fading from 40 m and gone by 60 m. Expecting "the distant ones should disappear" without entering values won't work. This isn't automatic — it's a number you choose.

A two-row completed node graph: the top row "Set up one Component" covers Instanced Static Mesh, Static Mesh and Cull Distance (Start 4000 / End 6000), wrapping into the bottom row "Just add positions" with For Loop, Make Transform and Add Instance
BP_ForestB (Event Graph)
Event BeginPlay
  → For Loop (First = 0, Last = 2999)
      Loop Body:
        // Lay out a grid (50 columns x 60 rows, 400 spacing)
        x = (Index % 50) * 400.0
        y = (Index / 50) * 400.0
        t = MakeTransform(
              Location = (x, y, 0.0),
              Rotation = (0, 0, RandomFloatInRange(0, 360)),   // vary the facing
              Scale    = (1.0, 1.0, RandomFloatInRange(0.8, 1.2)))
        → Trees.AddInstance(Transform = t, bWorldSpace = false)

Checking it

Swap in BP_ForestB, launch Standalone from exactly the same position and orientation as ①, and bring up stat unit.

The view looks essentially identical (3,000 rocks in a grid), yet Draw has clearly dropped. stat rhi's DrawPrimitive calls shows nearly 3,000 commands falling dramatically.

Now check step ③. Pull the camera back and move away from the cluster. Past the End Cull Distance of 6000 (60 m), the distant rocks disappear. Change 6000 to 3000 and you'll watch the vanishing point move closer. That's the values you set doing exactly what you told them — not magic.

Troubleshooting:

  • Nothing renders → You forgot to assign Static Mesh. Check the Component's details panel
  • They're all stacked in one spotMake Transform's Location is the same every iteration. Confirm you're computing coordinates from Index
  • Draw doesn't drop → The original mesh most likely has Nanite enabled. It's already batched, so this conversion has nothing to gain
  • Distant rocks don't disappearInstance Start / End Cull Distance are still 0. Zero means "don't thin," so enter values
  • The numbers differ each run → The camera moved. Measure from the same place

Two things to take away.

  • It only helps when Draw is the problem: confirm Draw is the outlier in stat unit first. It does nothing for GPU-bound slowness
  • Don't convert anything that needs its own behavior: instances aren't Actors, so no per-instance collision events or logic. Move only background dressing to ISM
  • Distance thinning is a value you enter: Instance Start / End Cull Distance default to 0 (don't thin). When "I converted it and nothing got faster," look here first

If you'd rather paint the trees by hand in the editor, the Foliage tool puts the same machinery behind a UI (→ Landscape Basics).


Bonus: Good to Know for Later

PCG uses this internally. The meshes PCG's Static Mesh Spawner places are instanced under the hood. "Thousands of trees stay relatively cheap" is this article's machinery at work. If you use PCG, you're already benefiting.

Consolidate materials. Separate materials for leaves and trunk split you into two batches immediately. Where possible, give background dressing a single material to cut batch count (atlasing textures is the same idea).

Pair it with an editor tool. An Editor Utility Widget that "converts the selected Actors into instances" lets you optimize existing levels in bulk. Valuable when you already hand-placed a lot of Actors.

Collision still works. Instances can't have individual events, but collision does function per instance — the player still bumps into the trees. "Solid, but doesn't react individually" is exactly right for background obstacles.


Summary

  • stat unit's Draw is command count. More objects, more commands (measure the real number)
  • Instancing means saying "this shape, at these 3,000 places" once
  • When in doubt, start with ISM. Modern ISM also culls and LODs per instance on the GPU. HISM is for thousands that basically never move
  • Distance thinning means entering Instance Start / End Cull Distance yourself (they default to 0 = don't thin)
  • Add Instance returns an index, and Remove Instance shifts them
  • With Nanite, the gain is small. Measure, convert, measure again

The value here is dropping Draw without cutting a single tree. What's the most numerous mesh in your level?