Culling
Culling skips rendering what the camera cannot see — cut draw calls by not drawing objects outside the view frustum or fully hidden behind other occluders.
Imagine...
Culling is a smart stage manager. Instead of lighting every actor on stage, they only light the ones visible from the seats. Behind the curtain? Lights off. Off-angle? Lights off. The GPU never hears about them — thousands of draw calls saved every frame.
The concept in detail
Frustum Culling is the baseline — Unity tests the six planes of the camera frustum (the truncated pyramid from FOV and near/far clip) against each Renderer’s bounding box. If the bounds sit fully outside → skip the draw call. Built-in, always on, no setup.
Occlusion Culling is the next step — skip objects that are inside the frustum but fully hidden. Unity precomputes occlusion data (Umbra) in a bake: Window → Rendering → Occlusion Culling → Bake. Mark occluders and occludees in the Static dropdown. Strong for indoor scenes, weaker for open world.
LOD (Level of Detail) is related — instead of culling entirely, a LOD Group lowers mesh complexity with distance. LOD0 = full detail (near), LOD1 = medium, LOD2 = simple, LOD3 = billboard, Culled = hidden. A quality/performance trade-off by distance.
Backface Culling is GPU-level — triangles whose
normals face away from the camera are dropped. On by default in every shader, saving ~50%
fragment work on closed meshes. Disable with “Two Sided” on the Material or
Cull Off in ShaderLab.
Types of culling
Frustum Culling
Objects outside the camera view are skipped entirely. Automatic, no setup needed. Effective in 100% of projects.
Camera.layerCullDistances[]
Occlusion Culling
Objects hidden behind walls/buildings are skipped. Requires baking and marking Static. Best for indoor scenes.
Static → Occluder/Occludee
LOD Group
Lowers polygon count when the object is far from the camera. LOD0→LOD3→Culled. Requires preparing multiple meshes. Good for open world.
LODGroup component
Hands-on steps
Frustum Culling: tighten Camera Far Clip
Camera → Far Clip Plane: drop from 1000 to 200–500 to cull distant objects. Hide the cut with fog.
Mark Static for Occlusion Culling
Select walls/floors/ceilings → Inspector → Static → Occluder Static and Occludee Static.
Bake occlusion data
Window → Rendering → Occlusion Culling → Bake. Add an Occlusion Area to control the bake volume.
Add a LOD Group
Add Component → LOD Group → drop LOD0/1/2 meshes into each slot, tune % screen size.
Measure with Rendering Statistics
Game View → Stats: watch "Batches" and "Tris" as you move the camera. Occlusion Culling Visualization in Scene view.
Interactive simulator
Orbit the camera around the scene to see which objects frustum-cull and occlusion-cull.
Visible: 0
Culled: 0
Code example
BasicSet per-layer cull distances and check renderer visibility.
using UnityEngine;
public class CullingSetup : MonoBehaviour
{
void Start()
{
Camera cam = Camera.main;
// Per-layer cull distance
// 32 entries (32 layers) — 0 = use Far Clip
float[] distances = new float[32];
distances[8] = 50f; // Layer 8 "SmallProps": cull after 50m
distances[9] = 150f; // Layer 9 "MediumProps": cull after 150m
distances[10] = 0f; // Layer 10: use Camera.farClipPlane
cam.layerCullDistances = distances;
// Spherical culling: from camera center, not frustum planes
cam.layerCullSpherical = true;
}
// Is this renderer currently visible to a camera?
void CheckVisibility(Renderer rend)
{
if (rend.isVisible)
Debug.Log(rend.name + " is being rendered");
}
}Code example
AdvancedConfigure a LOD Group from code and force a LOD level (debug / cutscene).
using UnityEngine;
public class DynamicLOD : MonoBehaviour
{
public Renderer[] lod0, lod1, lod2;
LODGroup group;
void Awake()
{
group = GetComponent<LODGroup>();
if (group == null)
group = gameObject.AddComponent<LODGroup>();
// Configure LOD levels from code
LOD[] lods = new LOD[3];
lods[0] = new LOD(0.5f, lod0); // 50%+ screen → LOD0
lods[1] = new LOD(0.15f, lod1); // 15-50% → LOD1
lods[2] = new LOD(0.03f, lod2); // <3% → LOD2, <0 → culled
group.SetLODs(lods);
group.RecalculateBounds();
}
// Force a specific LOD (debug or cutscene close-up)
public void ForceLOD(int level)
{
group.ForceLOD(level); // -1 = disable force, use automatic
}
}📌 Quick recap
- ▸Frustum Culling: automatic, no setup
- ▸Occlusion Culling: needs a bake, best indoors
- ▸LOD Group: lower poly count with distance
- ▸layerCullDistances: per-layer cull range
- ▸renderer.isVisible: true while being rendered
⚠️ Common mistakes
❌ Occlusion baked but nothing culls
Forgot Camera → Occlusion Culling = true
✅ Camera Inspector → Rendering → check Occlusion Culling
❌ LOD pop (mesh snaps when zooming)
LOD transitions are too wide, no LOD Fade
✅ Set "Fade Mode = Cross Fade" on the LOD Group to blend