← Cheatsheets
Tags: unity, performance, optimization, lod, occlusion-culling, object-pooling, draw-calls, batching, game-dev
Last updated: 2026-06-27
Performance Optimization Cheatsheet
Quick Reference
| Technique | Solves | Impact |
| LOD (Level of Detail) | High poly counts at distance | High |
| Occlusion Culling | Rendering hidden geometry | High |
| Object Pooling | Instantiation GC spikes | High |
| Static/Dynamic Batching | Draw call count | Medium–High |
| GPU Instancing | Repeated meshes (trees, rocks) | Medium |
| Texture Atlasing | Material batching | Medium |
LOD (Level of Detail)
LOD Group Setup
- Add
LOD Group component to mesh parent.
- Define LOD levels (0 = full detail, 1/2/3 = reduced).
- Assign lower-detail meshes per level.
- Set transition percentages.
| LOD | Screen % | Triangle Count (example) |
| LOD0 | 100–60% | Full (10,000) |
| LOD1 | 60–30% | Medium (3,000) |
| LOD2 | 30–10% | Low (500) |
| Culled | <10% | 0 |
LODGroup lodGroup = GetComponent<LODGroup>();
LOD[] lods = lodGroup.GetLODs();
lods[0].screenRelativeTransitionHeight = 0.1f;
lodGroup.SetLODs(lods);
Occlusion Culling
Setup
- Window → Rendering → Occlusion Culling.
- Mark static objects as Occluder Static / Occludee Static.
- Bake occlusion data.
- Enable Occlusion Culling on Camera.
Key Settings
| Setting | Effect |
| Smallest Occluder | Minimum size to block (smaller = more occluders, higher bake) |
| Smallest Hole | Gap size that counts as transparent |
| Backface Threshold | % backfaces to consider occluder |
Manual Checks
if (Vector3.Distance(player.position, transform.position) > cullDistance)
return;
if (!IsVisibleFrom(renderer, Camera.main))
return;
Object Pooling
Basic Pool
public class ObjectPool<T> where T : Component {
Queue<T> pool = new();
T prefab;
public ObjectPool(T prefab, int initialSize) {
this.prefab = prefab;
for (int i = 0; i < initialSize; i++) {
T obj = GameObject.Instantiate(prefab);
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
public T Get(Vector3 pos, Quaternion rot) {
T obj = pool.Count > 0 ? pool.Dequeue() :
GameObject.Instantiate(prefab);
obj.transform.SetPositionAndRotation(pos, rot);
obj.gameObject.SetActive(true);
return obj;
}
public void Return(T obj) {
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
Unity Built-In Pool (2021+)
using UnityEngine.Pool;
ObjectPool<Bullet> bulletPool = new(
createFunc: () => Instantiate(bulletPrefab),
actionOnGet: (b) => b.gameObject.SetActive(true),
actionOnRelease: (b) => b.gameObject.SetActive(false),
actionOnDestroy: (b) => Destroy(b.gameObject),
defaultCapacity: 20
);
Bullet b = bulletPool.Get();
bulletPool.Release(b);
Pooling Tips
- Pre-warm the pool during loading screen.
- Don’t pool unique objects (player, UI).
- Pool frequently created/destroyed objects: bullets, particles, enemies, VFX, sound sources.
- Return objects to pool after use, not destroy.
Draw Call Batching
Static Batching
- Mark objects as
Static → Batching Static.
- Unity combines static meshes at build time.
- Pros: zero runtime cost. Cons: more memory, non-moving only.
Dynamic Batching
- Automatic for small meshes (<300 verts).
- Enabled in Player Settings → Dynamic Batching.
- Pros: works on moving objects. Cons: per-frame CPU cost, vertex limit.
GPU Instancing
- Draw many copies of same mesh in one draw call.
- Enable on material:
Enable GPU Instancing.
Graphics.DrawMeshInstanced(mesh, 0, material, matrices, count);
| Method | Max Objects | Moving? | Memory |
| Static Batching | Unlimited | No | Higher memory |
| Dynamic Batching | Per-frame limit | Yes | Higher CPU |
| GPU Instancing | ~1023/batch | Yes | Lower memory |
SRP Batcher (URP/HDRP)
- Enabled by default in URP/HDRP.
- Batches by shader variant, not mesh.
- Requires compatible shaders (Shader Graph, Unity).
- Check in Frame Debugger: “SRP Batcher” entries.
Profiling
Tools
| Tool | What It Shows |
| Profiler | CPU/GPU/Rendering/Memory per frame |
| Frame Debugger | Every draw call and why it happened |
| Memory Profiler | All allocations, managed + native |
| Rendering Debugger (URP/HDRP) | Overdraw, light count, shadow maps |
| Physics Debugger | Collider geometry, contacts |
Key Metrics
| Metric | Target (PC) | Target (Mobile) |
| Draw Calls | <2000 | <200 |
| SetPass Calls | <50 | <20 |
| Triangles | <1M visible | <100K visible |
| Batches (SRP) | <500 | <100 |
| GPU ms | <16ms (60fps) | <33ms (30fps) |
| Managed Alloc/frame | <1KB goal | <1KB goal |
Profiler.BeginSample("MyExpensiveCode");
// Code to profile
Profiler.EndSample();
Common Bottleneck Fixes
| Symptom | Check | Fix |
| High draw calls | Frame Debugger | Enable SRP Batcher, atlasing, instancing |
| CPU spikes | Profiler → CPU Usage | Object pool, cache GetComponent, reduce Find() |
| GPU bound | GPU profiler | Reduce overdraw, shadows, post-processing res |
| GC alloc spikes | Memory Profiler | Pool objects, cache lists, avoid string concat |
| Physics spikes | Physics Debugger | Reduce collider count, lower solver iterations |
| Loading time | Profiler → Loading | Split into additive scenes, async loading |