This commit is contained in:
Jérôme Bousquié
2026-09-25 20:06:10 +02:00
parent 24fbafc810
commit d4c2d93fc5
31 changed files with 988 additions and 881 deletions
+99
View File
@@ -0,0 +1,99 @@
# 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)