Bloom
Purpose
Bloom is a three-step recipe — threshold the bright pixels, blur them, add them back on top — and that threshold is the knob that decides whether the effect feels like polish or feels like fog.
Key insight
Cameras and eyes can't cleanly resolve very bright light; it leaks into its surroundings. Shaders fake this with a pipeline. Step one: build a second image that only contains pixels above some brightness threshold — everything else goes black. Step two: blur that image heavily (downsample, blur, upsample). Step three: add the blurred bright-only image on top of the original.
The original scene is untouched; the glow is a second image layered over it. Because a pixel can only see its own coordinate, the blur is what gives each pixel knowledge of its bright neighbors — the multi-pass contract from Lesson 1. Bloom is also why scene values are allowed to exceed 1.0 (HDR): if every pixel maxed at 1.0, nothing would ever cross the threshold.
Break it
Pull radiance to 0. Every pixel crosses the threshold, so every pixel bleeds into its neighbors. The image looks like it's been hit with a soft-focus filter — contrast collapses, blacks lift, the scene looks underwater. Teaches: bloom is a soft-blur layered over the source; without selectivity, it becomes the entire image. The threshold isn't a minor knob — it's the reason bloom reads as "highlights glowing" instead of "the scene is blurry."
Direct Claude
// Base scene: sphere (emissive) + cube + ground plane under a sun.
// Emissive color on the sphere is (5, 3, 1) — ABOVE 1.0.
// That's HDR: raw scene values can exceed the display's 0-1 range,
// which is what makes bloom possible at all.
const renderTarget = new THREE.WebGLRenderTarget(w, h, {
type: THREE.HalfFloatType, // float precision so values > 1 survive
});
const composer = new EffectComposer(renderer, renderTarget);
composer.addPass(new RenderPass(scene, camera));
// --- The three-step bloom recipe ---
// UnrealBloomPass does all of it in one pass object:
// step 1: threshold (keep only pixels above `threshold`)
// step 2: blur (mip-chain downsample + gaussian)
// step 3: add the blurred bright-only image back on top
const bloom = new UnrealBloomPass(
new THREE.Vector2(w, h),
1.2, // strength — how much glow adds in
0.6, // radius — how far the glow spreads
0.85 // THRESHOLD — this is u_radiance below
);
composer.addPass(bloom);
// Tone-map + display (bloom runs BEFORE this, on the raw HDR values)
composer.addPass(new OutputPass());
// The slider just updates the threshold:
function setRadiance(v) { bloom.threshold = v; }