refactor examples
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# 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).
|
||||
@@ -10,7 +10,7 @@ The scene holds a single camera, read by the engine every frame to write the vie
|
||||
matrices into the frame buffer (aspect recomputed from the window size).
|
||||
|
||||
```rust
|
||||
use wsg_lib::resources::Camera;
|
||||
use wsg_lib::camera::Camera;
|
||||
use glam::Vec3;
|
||||
|
||||
app.scene.set_camera(Camera::new(
|
||||
@@ -37,7 +37,7 @@ app.scene.set_camera(Camera::new(
|
||||
bounded to `[0.1, 100]`), `target` (target point).
|
||||
|
||||
```rust
|
||||
use wsg_lib::resources::CameraController;
|
||||
use wsg_lib::camera::CameraController;
|
||||
|
||||
let mut ctrl = CameraController::default(); // target at origin, distance 3, front view
|
||||
ctrl.orbit(dx, dy); // mouse drag: yaw/pitch (bounded pitch, no poles)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# Émissive + Exposure
|
||||
|
||||
## Principe
|
||||
|
||||
Deux features complémentaires (Étape 22) :
|
||||
|
||||
| Feature | Effet | Coût |
|
||||
|---------|-------|------|
|
||||
| **Exposure** (6.1) | Multiplie la luminance avant la courbe de tone mapping | Zéro si HDR inactif |
|
||||
| **Emissive** (6.2) | Ajoute une couleur émise (indépendante des lumières) | Zéro si `emissive = [0,0,0,0]` |
|
||||
|
||||
## Exposure
|
||||
|
||||
### API
|
||||
|
||||
```rust
|
||||
// Initialisation (optionnel, default = 1.0)
|
||||
let app = AppBuilder::new()
|
||||
.with_hdr(ToneMapper::Aces)
|
||||
.with_exposure(1.5) // démarre plus clair
|
||||
.build().await?;
|
||||
|
||||
// Runtime (dans update())
|
||||
app.set_exposure(app.exposure() * 1.1); // +1 "stop"
|
||||
app.set_exposure(1.0); // reset
|
||||
```
|
||||
|
||||
### Comportement
|
||||
|
||||
- L'exposure est un **multiplicateur** appliqué à la texture HDR avant la courbe de tone mapping.
|
||||
- `exposure = 2.0` → l'image est 2× plus claire (comme ouvrir le diaphragme d'un photo).
|
||||
- `exposure = 0.5` → l'image est 2× plus sombre.
|
||||
- Clampé à `[0.01, 10.0]` pour éviter les valeurs dégénérées.
|
||||
- **N'a d'effet que si HDR est actif** (`with_hdr(...)`). En LDR, la valeur est ignorée.
|
||||
|
||||
### Clavier (demo)
|
||||
|
||||
| Touche | Effet |
|
||||
|--------|-------|
|
||||
| `+` | ×1.1 (plus clair) |
|
||||
| `-` | ÷1.1 (plus sombre) |
|
||||
| `0` | Reset à 1.0 |
|
||||
|
||||
## Emissive
|
||||
|
||||
### API
|
||||
|
||||
```rust
|
||||
use wsg_lib::resources::Material;
|
||||
|
||||
// Créer un matériau avec émissivité
|
||||
let mut mat = /* ... */;
|
||||
mat.emissive = [1.0, 0.3, 0.1, 1.5]; // orange, intensité 1.5 (> 1.0 = glow HDR)
|
||||
```
|
||||
|
||||
### Format
|
||||
|
||||
`emissive = [r, g, b, intensity]` :
|
||||
|
||||
- **rgb** : la couleur de l'émission (même espace que la couleur base du vertex)
|
||||
- **a (intensity)** : le multiplicateur. `1.0` = couleur normale, `> 1.0` = surbrillance (ne se voit qu'en HDR)
|
||||
|
||||
### Formule shader
|
||||
|
||||
```
|
||||
final_color = lit + base_color * emissive.rgb * emissive.a
|
||||
```
|
||||
|
||||
- L'émission est **additive** : visible même dans le noir total (pas de lumière nécessaire).
|
||||
- Elle est **indépendante des ombres** : un objet émissif ne projette pas d'ombre et n'est pas ombragé.
|
||||
- `emissive = [0,0,0,0]` (default) → aucun changement (non-régression garantie).
|
||||
|
||||
### Cas d'usage
|
||||
|
||||
| Usage | Valeur |
|
||||
|-------|--------|
|
||||
| LED / indicateur | `[0, 1, 0, 1.0]` (vert, intensité normale) |
|
||||
| Flamme / soleil | `[1, 0.8, 0.2, 3.0]` (orange, glow HDR) |
|
||||
| Neon | `[0, 0.5, 1, 2.5]` (cyan, glow) |
|
||||
| Inactif | `[0, 0, 0, 0]` (default) |
|
||||
|
||||
### Clavier (demo)
|
||||
|
||||
| Touche | Effet |
|
||||
|--------|-------|
|
||||
| `E` | Toggle glow orange sur la sphère/cylindre |
|
||||
|
||||
## Interactions
|
||||
|
||||
| Combination | Résultat |
|
||||
|-------------|----------|
|
||||
| Emissive + HDR + ACES | Glow doux, highlights roll off (le plus joli) |
|
||||
| Emissive + LDR | Clamped à 1.0 (pas de glow, mais couleur visible dans le noir) |
|
||||
| Emissive + shadows | L'objet émissif n'est PAS ombragé (l'émission bypass le shadow term) |
|
||||
| Exposure + Emissive | L'exposure amplifie aussi l'émission (cohérent : tout est dans la texture HDR) |
|
||||
|
||||
## Non-régression
|
||||
|
||||
- **Emissive** : `[0,0,0,0]` par défaut → le shader additionne `base * 0 * 0 = 0` → aucun changement.
|
||||
- **Exposure** : `1.0` par défaut → `pow(color, 1/1) = color` → aucun changement.
|
||||
- Les deux sont **opt-in** : sans `with_hdr(...)` ni `emissive != 0`, le pipeline est identique à l'état précédent.
|
||||
|
||||
## Limitations (MVP)
|
||||
|
||||
- L'emissive est **par matériau**, pas par vertex (pas de gradient d'émission dans un mesh).
|
||||
- L'emissive est **statique** à la création du matériau (changer `mat.emissive` requiert de re-registrer le matériau via `add_material`).
|
||||
- Pas de **bloom** (Étape 23) : le glow HDR est visible mais pas "flou" / diffusé.
|
||||
Reference in New Issue
Block a user