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

· 1 min read · tsl, webgpu, post-processing, performance, shaders

Glowing neon tubes and signs blooming softly over a wet street at night in a WebGPU racing game

A neon city lives on bloom. In Threejs-Punk, 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. The game is on its project page.

Related projects