94 lines
3.3 KiB
Markdown
94 lines
3.3 KiB
Markdown
# Bloom (Étape 23)
|
||
|
||
Le **bloom** est un post-process qui crée un effet de "glow" autour des zones brillantes de
|
||
l'image. Les pixels dont la luminance dépasse un seuil sont extraits, floutés, puis ajoutés
|
||
à l'image originale.
|
||
|
||
> **Prérequis** : le bloom nécessite l'HDR (`AppBuilder::with_hdr`). Sans HDR, les valeurs
|
||
> sont déjà clampées à [0,1] et il n'y a rien de "brillant" à extraire.
|
||
|
||
## Activation
|
||
|
||
```rust
|
||
use wsg_lib::prelude::*;
|
||
|
||
let app = AppBuilder::new()
|
||
.with_hdr(ToneMapper::Aces) // requis
|
||
.with_bloom(BloomConfig {
|
||
threshold: 1.0, // seuil de luminance HDR
|
||
knee: 0.5, // largeur du soft-knee
|
||
intensity: 0.8, // intensité du glow
|
||
radius: 4.0, // rayon du blur (pixels, demi-rés)
|
||
..Default::default()
|
||
})
|
||
.build()
|
||
.await?;
|
||
```
|
||
|
||
## `BloomConfig`
|
||
|
||
| Champ | Type | Défaut | Description |
|
||
|-------|------|--------|-------------|
|
||
| `threshold` | `f32` | `1.0` | Seuil de luminance (unités HDR linéaires). Seuls les pixels > seuil contribuent au bloom. |
|
||
| `knee` | `f32` | `0.5` | Largeur du soft-knee. Plus grand = transition plus douce. |
|
||
| `intensity` | `f32` | `0.8` | Multiplicateur appliqué au résultat flouté avant addition à l'HDR. |
|
||
| `radius` | `f32` | `4.0` | Rayon du blur en pixels (à la demi-résolution). Plus grand = glow plus étendu. |
|
||
|
||
## Mise à jour runtime
|
||
|
||
```rust
|
||
// Dans le handler (fn update):
|
||
if app.bloom_enabled() {
|
||
app.set_bloom_config(BloomConfig {
|
||
intensity: new_intensity,
|
||
..app.bloom_config()
|
||
});
|
||
}
|
||
```
|
||
|
||
Les changements prennent effet au frame suivant (les uniforms sont ré-écrits chaque frame).
|
||
|
||
## Pipeline (4 passes GPU)
|
||
|
||
```
|
||
Scene ──→ HDR (full res, Rgba16Float)
|
||
│
|
||
├──→ [1] Threshold (full → half res)
|
||
│ Soft-knee: smoothstep(knee, knee+1, lum)
|
||
│
|
||
├──→ [2] Blur H (half res)
|
||
│ 9-tap Gaussian séparable, direction = (1/w, 0)
|
||
│
|
||
├──→ [3] Blur V (half res)
|
||
│ 9-tap Gaussian séparable, direction = (0, 1/h)
|
||
│ (ping-pong: écrit dans la texture bright)
|
||
│
|
||
└──→ [4] Composite (full res)
|
||
output = HDR + bloom × intensity
|
||
(écrit dans une 3e texture full-res)
|
||
│
|
||
▼
|
||
Tone Mapping (lit le composite)
|
||
│
|
||
▼
|
||
Surface (sRGB)
|
||
```
|
||
|
||
## Coût
|
||
|
||
- **Sans bloom** (défaut) : zéro overhead. Le TM lit directement la texture HDR.
|
||
- **Avec bloom** : 4 passes supplémentaires (1 full-res + 3 half-res) + 3 textures
|
||
intermédiaires. Le coût est modéré car le blur est en demi-résolution.
|
||
|
||
## Non-régression
|
||
|
||
- `with_bloom()` sans `with_hdr()` → warning + no-op (le bloom est ignoré).
|
||
- Sans `with_bloom()` → le TM lit la texture HDR directement (comportement Étape 20 inchangé).
|
||
|
||
## Limitations (MVP)
|
||
|
||
- Un seul niveau de mip (pas de multi-mip "soft" bloom à la Unreal).
|
||
- Pas de directional bloom.
|
||
- Le blur est un Gaussian 9-taps (qualité suffisante pour un glow "soft").
|
||
- Pas de bloom séparé par couche (pas de "bloom mask" par matériau).
|