Files
wsg/docs/user/effects/bloom.md
T
Jérôme Bousquié d4c2d93fc5 eng doc
2026-09-25 20:06:10 +02:00

3.4 KiB
Raw Blame History

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

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

// 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").