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

136 lines
3.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)