100 lines
3.4 KiB
Markdown
100 lines
3.4 KiB
Markdown
# 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)
|