# Occlusion culling on the CPU for an instanced city

> three's occlusion queries do not fit an instanced city. Here is the small software depth buffer that culled 56-76% of Threejs-Punk's instances.

Canonical page: https://andersonmancini.dev/blog/threejs-punk-occlusion-culling

Published: 2026-08-22 · 1 min read · Tags: three.js, performance, culling, webgpu, game-dev

From a street in [Threejs-Punk](https://andersonmancini.dev/blog/threejs-punk-racing-game.md), most of the city inside the frustum is behind the first row of facades. A ray test says about 90% of the instances. Drawing them is wasted work.

## Why the built-in queries do not fit

three's `occlusionTest` wraps one draw. In this city, one draw is an (asset, LOD) mesh spread over the whole map, a query needs a draw to get a result, and the answer arrives frames later. That is the wrong tool for an instanced city.

## A software depth buffer

Instead, an occlusion culler rasterizes the nearby collider boxes (one per building hull slab, roughly 500 to 700 within 260 m) into a **256x128 software depth buffer**. When the instance buffers are rebuilt, every instance whose world box lies entirely behind that buffer is left out. Meshes left empty stop drawing.

The city re-renders the buffer and rebuilds every layer together every 2 m, 2 degrees or 300 ms, about 1 ms of JavaScript.

## It errs toward drawing

A wrong cull is worse than a wasted draw, so the culler is conservative:

- occluders shrink by 1 m,
- tested boxes grow by 1 m,
- the buffer covers a 1.3x wider field of view,
- nothing within 18 m is culled.

The planar mirror reuses the view's lists.

## What it bought

Measured on the project's harness: **56 to 76% of the city instances culled**, from 2.26 to 1.72 M triangles and about 25 fewer draws per frame. Screenshots with and without culling, still and while driving or turning at 80 to 105 km/h, differ only by the animation noise.

It can be A/B tested from the console, and it has knobs for the on/off state and the buffer resolution, like every optional cost in the project.

## Related

This is one of several wins in the [performance work](https://andersonmancini.dev/blog/threejs-punk-fewer-uploads-faster-frames.md) of the game. The city itself is in [the generated city post](https://andersonmancini.dev/blog/threejs-punk-city-from-an-image.md), and the game is on its [project page](https://andersonmancini.dev/projects/threejs-punk.md).

## Related projects

- [Threejs-Punk](https://andersonmancini.dev/projects/threejs-punk.md) — Fully featured Racing Game
