78 lines
2.9 KiB
Markdown
78 lines
2.9 KiB
Markdown
# HDR & Tone Mapping
|
|
|
|
> **Étape 20** — Opt-in HDR rendering with tone mapping.
|
|
|
|
## What it does
|
|
|
|
By default, the WSG renderer draws directly to the window's sRGB surface. Color values
|
|
above 1.0 are **clipped** (saturated to white) — you lose all information in bright areas.
|
|
|
|
When HDR is enabled, the pipeline becomes:
|
|
|
|
```
|
|
Main pass → offscreen Rgba16Float texture (unbounded float)
|
|
TM pass → fullscreen triangle samples HDR texture, applies curve, writes to sRGB surface
|
|
```
|
|
|
|
The tone mapping **compresses** the [0, ∞) range to [0, 1] with a perceptual curve,
|
|
so bright areas are smoothly rolled off instead of clipping.
|
|
|
|
## Enabling HDR
|
|
|
|
```rust
|
|
use wsg_lib::core::ToneMapper;
|
|
use wsg_lib::app::AppBuilder;
|
|
|
|
let app = AppBuilder::new()
|
|
.title("My HDR App")
|
|
.with_hdr(ToneMapper::Aces) // ← enables HDR
|
|
.build()
|
|
.await?;
|
|
```
|
|
|
|
Without `.with_hdr(...)`, the renderer operates in LDR mode (direct to surface, zero overhead).
|
|
|
|
## Tone mapping curves
|
|
|
|
| Variant | Curve | Use case |
|
|
|---------|-------|----------|
|
|
| `ToneMapper::Aces` | ACES Filmic (Narkowicz 2015) | Cinematic look, soft highlight rolloff, good contrast |
|
|
| `ToneMapper::Reinhard` | `x / (1 + x)` | Simple, flat; less contrast but computationally trivial |
|
|
|
|
The curve is **compiled into the pipeline** at construction time (one WGSL entry point
|
|
per variant) — there is no runtime branching cost.
|
|
|
|
## Cost
|
|
|
|
| HDR state | Extra per-frame cost |
|
|
|-----------|---------------------|
|
|
| Disabled (default) | **Zero** — no texture, no pass, no pipeline |
|
|
| Enabled | +1 fullscreen render pass (triangle, 3 verts) + 1 offscreen texture (same size as window) |
|
|
|
|
The extra pass is negligible on any GPU (a few hundred microseconds). The offscreen
|
|
texture costs ~12 bytes/pixel of VRAM (RGBA16F = 8 bytes/px + the surface's own buffer).
|
|
|
|
## How it works (technical)
|
|
|
|
- **Offscreen texture**: `Rgba16Float`, same size as the window. Created in `Renderer::new`,
|
|
recreated on resize.
|
|
- **Main pass**: the color attachment targets the HDR texture instead of the surface.
|
|
The `standard_shader.wgsl` fragment output (linear float, unbounded) is stored as-is.
|
|
- **TM pass**: a fullscreen triangle (3 vertices, no vertex buffer) samples the HDR texture,
|
|
multiplies by exposure (currently fixed at 1.0), applies the tone curve, and writes to
|
|
the sRGB surface. The hardware performs the linear→sRGB gamma conversion automatically
|
|
(the surface format is `Rgba8UnormSrgb`).
|
|
- **No double gamma**: the shader outputs linear [0,1]; the sRGB surface encoding is
|
|
handled by the rasterizer.
|
|
|
|
## Exposure
|
|
|
|
Currently fixed at 1.0 (no user control yet). A future step will expose an
|
|
`exposure` field in a `HdrConfig` struct for live adjustment.
|
|
|
|
## See also
|
|
|
|
- [Shadows](shadows.md) — the other opt-in visual feature
|
|
- [GPU-driven rendering](gpu-driven.md) — the compute pipeline that feeds the main pass
|
|
- [Examples](examples.md) — the `demo` example enables HDR by default
|