# Distance fog ## Principle Distance fog blends objects toward a predefined color based on their distance to the camera. It is the standard tool for: - **Hiding the rendered edge of the world** — the illusion of an infinite world (Skyrim, GTA, Minecraft) - **Adding depth** — a natural atmospheric effect - **Masking transitions** — tile loading, LOD pops ## Activation ```rust use wsg_lib::prelude::*; let app = AppBuilder::new() .with_fog(FogConfig::exponential2([0.7, 0.75, 0.85], 0.06)) .build() .await?; ``` Without `.with_fog()`, fog is disabled — **zero GPU cost** (the shader branch is never taken). ## Modes | Mode | Formula | Use | |------|---------|-----| | `Linear` | `saturate((far - d) / (far - near))` | Sharp cutoff between two distances | | `Exponential` | `exp(-density × d)` | Natural fog (forest, lake) | | `Exponential2` | `exp(-density² × d²)` | Gradual start, sharp cutoff — **ideal for masking** | ### Constructors ```rust // Linear: fade between near and far FogConfig::linear([0.7, 0.8, 0.9], 5.0, 50.0) // Exponential: natural fade FogConfig::exponential([0.6, 0.7, 0.8], 0.03) // Exponential²: world-edge masking FogConfig::exponential2([0.7, 0.75, 0.85], 0.08) ``` ## Parameters | Field | Type | Description | |-------|------|-------------| | `mode` | `FogMode` | Linear / Exponential / Exponential2 | | `color` | `[f32; 3]` | Fog color (RGB, linear space) | | `near` | `f32` | Start distance (linear mode only) | | `far` | `f32` | End distance, full fog (linear mode) | | `density` | `f32` | Density (exp / exp² modes). Typical: 0.01–0.3 | ### Choosing the color The fog color **must match the sky/clear color** for a seamless "infinite world" effect. With HDR + ACES, use linear values consistent with the tone mapping. ### Choosing the density (exp²) To mask the edge of the world at a distance `D`: ``` density ≈ 2.0 / D ``` Examples: - World visible up to 25 units → `density = 0.08` - World visible up to 50 units → `density = 0.04` - World visible up to 100 units → `density = 0.02` ## Runtime change ```rust // In update(): if key_pressed(KeyCode::Digit1) { app.renderer_mut().set_fog(Some(FogConfig::linear([0.7, 0.8, 0.9], 5.0, 30.0))); } if key_pressed(KeyCode::Digit4) { app.renderer_mut().set_fog(None); // disable } ``` The change takes effect on the next frame. ## Pipeline ```text Main pass (fragment shader) ↓ Lighting → final_rgb ↓ FOG: mix(final_rgb, fog_color, 1 - fog_factor) ← here ↓ → HDR texture / swapchain ↓ (Bloom) → Tone Mapping → surface ``` Fog is applied **before** tone mapping: HDR values stay unclamped, and the TM applies the ACES/Reinhard curve to the already-fogged result. Result: the fog is perceptually coherent. ## Compatibility | With | OK? | Note | |------|-----|------| | HDR + TM | ✅ | Fog before TM (recommended) | | Bloom | ✅ | Bloom extracts the bright areas of the post-fog result | | MSAA | ✅ | Independent (rasterizer vs fragment shader) | | GPU culling | ✅ | Independent (culling decides what to draw, fog decides the color) | | Shadows | ✅ | The shadow is computed before the fog | ## Limitations (v1) - **Scene-level only**: a single fog for the whole scene. Per-material fog would require an extra parameter in the per-object bind group. - **Euclidean distance**: no volumetric or directional fog. - **Fixed color**: no color gradient with distance. ## Example See `lib/examples/effects/fog.rs`: 15 cubes in a row + 5 spheres on an 80×80 plane, with runtime switching between the 3 modes. ```sh cargo run -p wsg-lib --example fog --features "all-prims" ``` ## Links - [User README](../README.md) · [HDR & tone mapping](hdr.md) · [Examples](../examples.md) - [Root README](../../../README.md)