# Bloom as one mip chain: 1.8 ms down to 0.4 ms

> three's UnrealBloom blurs five levels with separable Gaussians. A Call of Duty style mip chain does the same job in about a fifth of the time.

Canonical page: https://andersonmancini.dev/blog/threejs-punk-mip-bloom

Published: 2026-09-01 · 1 min read · Tags: tsl, webgpu, post-processing, performance, shaders

A neon city lives on bloom. In [Threejs-Punk](https://andersonmancini.dev/blog/threejs-punk-racing-game.md), three's UnrealBloom cost about 1.8 ms at 2880x1620, because it blurs each of its five levels with a separable Gaussian: 6 to 22 taps, two passes per level.

## The replacement

A **mip chain** in the style of the Call of Duty bloom (Jimenez, 2014):

- a **13-tap downsample** per mip,
- a **9-tap tent upsample** back up,
- one pass per level each way.

It costs about 0.4 ms, with the same surface as the stock node (`strength`, `radius`, `threshold` and so on), so it drops in.

## Details that matter

- The first downsample **thresholds every tap**. A neon tube thinner than a mip texel still blooms instead of vanishing.
- Matching UnrealBloom's look at the same preset takes a `gain` of 0.7 and a `spread` of 1.5.
- It can be A/B tested with a query parameter, so I could compare screenshots side by side.

## The emissive knee

The city also feeds bloom a dedicated emissive attachment, with a threshold and smooth knee. Sign neon is stored as hue only, and the runtime gives every sign one level, so even the dimmest sign clears the knee. Before that, half the neon-tube signs sat under it.

## Credit

The technique is Jorge Jimenez's from the Call of Duty: Advanced Warfare presentation (2014). Other frame-time work is in [the uploads post](https://andersonmancini.dev/blog/threejs-punk-fewer-uploads-faster-frames.md). 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
