2.9 KiB
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
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 inRenderer::new, recreated on resize. - Main pass: the color attachment targets the HDR texture instead of the surface.
The
standard_shader.wgslfragment 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 — the other opt-in visual feature
- GPU-driven rendering — the compute pipeline that feeds the main pass
- Examples — the
demoexample enables HDR by default