Skip to content

Occlusion Culling

The frustum answers is it in front of the camera. Inside a building, a town or a canyon, most of what survives that question is still invisible: a wall is in the way. Occluder lets you say which boxes sight does not pass through, and the renderer then stops collecting anything standing behind one.

world.insert(wall, Occluder, { halfExtents: { x: 400, y: 300, z: 20 } });

That is the whole component. It changes what a frame costs and never what it shows — the picture with occluders and the picture without them are the same pixels.

The box is authored, and it belongs inside the wall

Section titled “The box is authored, and it belongs inside the wall”

An occluder is not taken from the geometry beside it, and the reason is worth knowing: a mesh’s bounds contain the mesh. A bounding box around a pillar, an archway or a rock claims the empty air at its corners, and anything standing in that air would stop being drawn while the player can plainly see it.

So the rule is the opposite one:

Placing it:

  • halfExtents is in world units, measured from the entity’s position, and the entity’s rotation turns it. Scale is not read, the same as every other extent in the engine.
  • The editor draws the box in the viewport, so you can size it against the wall it is inside of.
  • It can sit on the wall’s own entity, or on an empty entity of its own — a building made of many meshes usually wants one big box rather than one per mesh.

Only draws a depth test would have hidden anyway. An object that ignores depth paints over the wall in front of it, and culling it would remove something the frame really does show — so sprites, UI, text and any surface whose material turns the depth test off are never refused by an occluder.

It is also a camera’s answer, not the scene’s:

  • Two cameras in one frame each ask their own occluders.
  • Shadows are not culled by it. What the player cannot see still casts; a caster dropped because the camera cannot see it would take its shadow off the ground with it.
  • An occluder the camera stands inside is ignored, rather than declaring the whole view blocked.

Three counters in the Profiler, and each answers a different question:

counter says
render.cull.occluders how many boxes this view took
render.cull.occluded how many renderables an occluder refused
render.culled every cull, the frustum’s included

A scene whose occluders stopped working shows it in the first two: zero boxes means they were never taken (turned past the near plane, or authored to nothing), while boxes taken and nothing occluded means they are not in front of anything.

The test runs on a small grid — about a sixty-fourth of the view across — and a box only claims a cell of it that it covers whole. Two consequences worth designing around:

  • An occluder smaller than roughly 2% of the screen hides nothing. Big shapes are what pay: walls, floors between storeys, buildings, cliffs.
  • An object that is hidden by the combination of two walls with a gap between them is still drawn. Each box answers for itself.

Occluder is about what is in the way. It does not unload anything — see streaming for that — and it does not simplify what is drawn, which is LODGroup. The three compose: streaming decides what exists, occlusion decides what is collected, LOD decides how much geometry the survivors cost.