# Bloom (Step 23) **Bloom** is a post-process that creates a "glow" effect around the bright areas of the image. Pixels whose luminance exceeds a threshold are extracted, blurred, then added back to the original image. > **Prerequisite**: bloom requires HDR (`AppBuilder::with_hdr`). Without HDR, values are already > clamped to [0,1] and there is nothing "bright" to extract. ## Activation ```rust use wsg_lib::prelude::*; let app = AppBuilder::new() .with_hdr(ToneMapper::Aces) // required .with_bloom(BloomConfig { threshold: 1.0, // HDR luminance threshold knee: 0.5, // soft-knee width intensity: 0.8, // glow intensity radius: 4.0, // blur radius (pixels, half-res) ..Default::default() }) .build() .await?; ``` ## `BloomConfig` | Field | Type | Default | Description | |-------|------|---------|-------------| | `threshold` | `f32` | `1.0` | Luminance threshold (linear HDR units). Only pixels above the threshold contribute to the bloom. | | `knee` | `f32` | `0.5` | Soft-knee width. Larger = smoother transition. | | `intensity` | `f32` | `0.8` | Multiplier applied to the blurred result before adding it to the HDR. | | `radius` | `f32` | `4.0` | Blur radius in pixels (at half resolution). Larger = wider glow. | ## Runtime update ```rust // In the handler (fn update): if app.bloom_enabled() { app.set_bloom_config(BloomConfig { intensity: new_intensity, ..app.bloom_config() }); } ``` Changes take effect on the next frame (the uniforms are re-written every frame). ## Pipeline (4 GPU passes) ``` Scene ──→ HDR (full res, Rgba16Float) │ ├──→ [1] Threshold (full → half res) │ Soft-knee: smoothstep(knee, knee+1, lum) │ ├──→ [2] Blur H (half res) │ 9-tap separable Gaussian, direction = (1/w, 0) │ ├──→ [3] Blur V (half res) │ 9-tap separable Gaussian, direction = (0, 1/h) │ (ping-pong: writes into the bright texture) │ └──→ [4] Composite (full res) output = HDR + bloom × intensity (writes into a 3rd full-res texture) │ ▼ Tone Mapping (reads the composite) │ ▼ Surface (sRGB) ``` ## Cost - **Without bloom** (default): zero overhead. The TM reads the HDR texture directly. - **With bloom**: 4 extra passes (1 full-res + 3 half-res) + 3 intermediate textures. The cost is moderate because the blur runs at half resolution. ## Non-regression - `with_bloom()` without `with_hdr()` → warning + no-op (the bloom is ignored). - Without `with_bloom()` → the TM reads the HDR texture directly (the Step 20 behavior is unchanged). ## Limitations (MVP) - A single mip level (no multi-mip "soft" bloom à la Unreal). - No directional bloom. - The blur is a 9-tap Gaussian (good enough for a "soft" glow). - No per-layer bloom (no per-material "bloom mask"). ## Links - [User README](../README.md) · [HDR & tone mapping](hdr.md) · [Examples](../examples.md) - [Root README](../../../README.md)