Unity Term Book
Graphics & Rendering

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

1

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.

2

Mark Static for Occlusion Culling

Select walls/floors/ceilings → Inspector → Static → Occluder Static and Occludee Static.

3

Bake occlusion data

Window → Rendering → Occlusion Culling → Bake. Add an Occlusion Area to control the bake volume.

4

Add a LOD Group

Add Component → LOD Group → drop LOD0/1/2 meshes into each slot, tune % screen size.

5

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

Visible Frustum Culled Occlusion Culled

Code example

Basic

Set 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

Advanced

Configure 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