Skip to content
← Back to Blog 3d-modeling

GPU Instancing and Draw Call Batching for Game Assets: Boost Performance in Unity and Unreal Engine 5

By BitSoul3D6 min read273 views

Your game runs great with one tree. Add two hundred and the frame rate tanks. If that sounds familiar, draw calls are your problem — and GPU instancing plus batching are the fix.

GPU Instancing and Draw Call Batching for Game Assets: Boost Performance in Unity and Unreal Engine 5

This guide breaks down exactly how instancing and batching work at the hardware level, how to configure them correctly in Unity and Unreal Engine 5, and what your 3D assets need to look like to take full advantage.

What Are Draw Calls and Why Do They Kill Performance?

Every time your CPU tells the GPU to render an object, that's a draw call. The call packages up mesh data, material parameters, and shader instructions and hands them off across the CPU–GPU bus. The cost isn't in the rendering itself — modern GPUs are massively parallel and can shade millions of triangles per frame. The bottleneck is the call overhead: state changes, API validation, driver work.

A desktop game running at 60 fps has about 16.6 ms per frame. A single draw call on a mid-range PC costs roughly 0.05–0.1 ms. That sounds trivial until you have 500 objects on screen — suddenly 25–50 ms of your 16.6 ms budget is gone before the GPU even starts drawing. Mobile is worse: draw call overhead can be 5–10× higher, making budgets of 50–100 calls realistic for sustained 60 fps.

The solution is to reduce the number of times the CPU talks to the GPU — not necessarily to reduce triangle count. Batching and instancing are the two primary techniques.

What Are Draw Calls and Why Do They Kill Performance? — illustrated

GPU Instancing: One Draw Call, Many Copies

GPU instancing renders multiple copies of the same mesh + material in a single draw call. The GPU receives the mesh once, then a list of per-instance data (transforms, colors, any custom properties) and stamps out all copies in parallel. No extra CPU overhead per copy.

Requirements for instancing to activate

  1. Identical mesh — same vertex buffer, same index buffer. LOD swaps count as different meshes.
  2. Identical material — same shader, same textures. Per-instance color variation is fine; per-instance texture swaps are not (use a texture array instead).
  3. Enable the instancing flag on the shader/material.

Unity: Static and Dynamic Instancing

Static batching (for non-moving objects):

// In Player Settings → Other Settings
// Enable: Static Batching ✓

// Mark GameObjects static in the Inspector:
// GameObject → Static → Batching Static ✓

Unity merges all static-batched meshes into a single VBO at build time. One draw call per material, regardless of object count. Trade-off: increased memory usage since meshes are duplicated in the combined buffer.

GPU instancing (for moving or dynamic objects):

// On the Material, enable:
// Material → Enable GPU Instancing ✓

// Or via script — pass a MaterialPropertyBlock for per-instance variation:
MaterialPropertyBlock props = new MaterialPropertyBlock();
props.SetColor("_BaseColor", instanceColor);
renderer.SetPropertyBlock(props);

Dynamic batching (legacy, small meshes only): Unity auto-batches meshes under ~300 vertices sharing the same material. Largely superseded by GPU instancing for modern projects — leave it enabled but don't design around it.

Checking your draw call count

Use Unity's Frame Debugger (Window → Analysis → Frame Debugger) to see exactly which draw calls are firing each frame, which ones were batched, and why others weren't. Look for "Draw Mesh (instanced)" entries — that's instancing working.

GPU Instancing in Unreal Engine 5

UE5 handles instancing through the Hierarchical Instanced Static Mesh (HISM) component, which is what foliage, Nanite, and the World Partition system use internally.

Using HISM directly

// C++: Add a HierarchicalInstancedStaticMeshComponent
UHierarchicalInstancedStaticMeshComponent* HISMComp =
    CreateDefaultSubobject<UHierarchicalInstancedStaticMeshComponent>(TEXT("TreeInstances"));
HISMComp->SetStaticMesh(TreeMesh);

// Add 500 instances with transforms
for (int32 i = 0; i < 500; i++)
{
    FTransform InstanceTransform;
    InstanceTransform.SetLocation(FVector(FMath::RandRange(-5000.f, 5000.f), FMath::RandRange(-5000.f, 5000.f), 0.f));
    HISMComp->AddInstance(InstanceTransform);
}

For Blueprints, place a Hierarchical Instanced Static Mesh component and call Add Instance from construction script or runtime.

Nanite and instancing

Nanite removes the per-triangle cost concern entirely — it streams only the geometry that covers actual screen pixels. But draw call overhead still matters for non-Nanite objects and materials. Enable Nanite on static meshes with right-click → Nanite → Enable, and combine it with HISM for the best of both worlds: zero overdraw from Nanite plus minimal draw calls from instancing.

Nanite and instancing — illustrated

Designing 3D Assets for Instancing

The biggest instancing mistake is building assets that look reusable but aren't — unique UV islands baked into the albedo, one-off material slots, or tiny geometry variations that force separate meshes.

Asset checklist for maximum batching potential

RequirementWhy It Matters
Single material slot per meshMultiple slots = multiple draw calls per instance
Tiling or atlas texturesAllows texture reuse across all instances
No per-mesh unique bakes in albedoBaked detail should be in normal/AO maps, not albedo
Consistent pivot placementMakes instance grid/scatter placement predictable
LOD chain presentPrevents high-poly meshes from instancing at distance
Mesh under LOD0 budgetHISM culls LODs, not individual triangles — high-poly LOD0 still costs

Variation without breaking batching

Use vertex colors to drive material variation (color tints, wetness, seasonal state) without swapping textures. In Blender, paint vertex colors per island; in the shader, sample them as a mask to lerp between two tiled textures. All instances stay on the same draw call.

For more variety at scale, use a texture array — pack 4–8 albedo variants into a Texture2DArray and pass a per-instance index via MaterialPropertyBlock (Unity) or a custom primitive data float (UE5). One material, full visual diversity.

Browse ready-to-instance environment and foliage packs on the BitSoul marketplace — all assets follow the single-material-slot convention required for automatic batching.

Profiling and Validating Your Setup

Don't guess — measure. Before optimizing, capture a baseline.

Unity profiling checklist:

UE5 profiling checklist:

A well-optimized scene with 500 instanced trees should show 1–2 draw calls for the foliage, not 500. If you're still seeing hundreds of calls for repeated objects, check: are the materials truly identical? Is instancing enabled on the shader? Are LOD transitions breaking instance groups?

Closing: Build Once, Render Everywhere

GPU instancing and draw call batching aren't micro-optimizations — they're the difference between a scene that runs and one that doesn't. The rules are simple: same mesh, same material, instancing on. The payoff is dramatic, especially for environments with repeated elements like rocks, trees, props, and modular architecture pieces.

Start with the assets that appear most often in your scenes. Audit their material slot count, check their texture setup, and run them through the profiler before and after. The numbers will convince you faster than any benchmark article.

Find game-ready, instancing-optimized 3D assets at BitSoul marketplace — every asset is tested for real-time engine compatibility.


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 Architecture Modern Bundle on the BitSoul marketplace and drop them straight into your project.

Tags: architecture

Skip the modeling — download it instead

A free BitSoul3D account gets you 2 GLB downloads every month for personal use plus 25 one-time AI Engine credits, no card required. PBR-textured GLB downloads with a full 3D preview before you buy, for Unreal, Unity, Godot or Blender — OBJ and 3D-printable STL come with any purchase or paid plan.

Browse 1,051 models — from $4.99 → or start free (2 downloads a month)