This commit is contained in:
Jérôme Bousquié
2026-09-25 14:54:27 +02:00
parent 54a482e354
commit 83daeb4c7d
3 changed files with 341 additions and 315 deletions
+82 -72
View File
@@ -1,28 +1,28 @@
# WSG — WGPU Simple Graphics Library
**WSG** (WGPU Simple Graphics) est une bibliothèque Rust qui wrap [wgpu](https://github.com/gfx-rs/wgpu) et [winit](https://crates.io/crates/winit) pour dessiner en 3D **sans toucher wgpu directement**.
**WSG** (WGPU Simple Graphics) is a Rust library that wraps [wgpu](https://github.com/gfx-rs/wgpu) and [winit](https://crates.io/crates/winit) to draw 3D **without touching wgpu directly**.
## Ce que vous obtenez
## What you get
- **Une fenêtre 3D en ~30 lignes** — pas de wgpu, pas de winit dans votre code
- **Éclairage Phong** (directional, point, spot) + **ombres portées** (shadow mapping)
- **HDR + Tone Mapping** (ACES Filmic / Reinhard) — opt-in, zéro coût si désactivé
- **Pipeline GPU-driven** — world matrices + frustum culling sur le GPU, indirect draws
- **LOD** (Level of Detail) — dégradation automatique de la géométrie selon la distance
- **Primitives procédurales** — cube, sphère, cylindre, cône, tore, plan
- **Import de fichiers** — parser OBJ intégré (glTF en cours)
- **Caméra orbitale** + input unifié (clavier/souris)
- **LOD, culling, HDR, ombres** : tout est **opt-in** — ce que vous n'activez pas ne coûte rien
- **A 3D window in ~30 lines** — no wgpu, no winit in your code
- **Phong lighting** (directional, point, spot) + **shadows** (shadow mapping)
- **HDR + Tone Mapping** (ACES Filmic / Reinhard) — opt-in, zero cost when disabled
- **GPU-driven pipeline** — world matrices + frustum culling on the GPU, indirect draws
- **LOD** (Level of Detail) — automatic geometry degradation based on distance
- **Procedural primitives** — cube, sphere, cylinder, cone, torus, plane
- **File import** — built-in OBJ parser (glTF in progress)
- **Orbital camera** + unified input (keyboard/mouse)
- **LOD, culling, HDR, shadows**: everything is **opt-in** — what you don't enable costs nothing
## Forces
## Strengths
| Force | Détail |
|-------|--------|
| **Zéro wgpu dans votre code** | L'API déclarative (`AppBuilder` + `AppHandler`) encapsule tout |
| **Opt-in = zéro coût** | Un effet non activé n'alloue rien, n'exécute rien |
| **Features Cargo** | Ne compilez que les primitives/import dont vous avez besoin |
| **Un seul shader** | Le `standard` shader (Phong) couvre 90 % des cas ; mode unlit pour la 2D |
| **GPU-driven** | Le CPU envoie des transforms, le GPU fait le reste (matrices, culling, draws) |
| Strength | Detail |
|----------|--------|
| **Zero wgpu in your code** | The declarative API (`AppBuilder` + `AppHandler`) encapsulates everything |
| **Opt-in = zero cost** | A disabled effect allocates nothing, executes nothing |
| **Cargo features** | Only compile the primitives/importers you need |
| **One shader** | The `standard` shader (Phong) covers 90% of cases; unlit mode for 2D |
| **GPU-driven** | CPU sends transforms, GPU does the rest (matrices, culling, draws) |
## Quickstart
@@ -31,9 +31,9 @@ use wsg_lib::prelude::*;
use wsg_lib::app::AppBuilder;
use wsg_lib::utils::WsgError;
struct MaScene;
struct MyScene;
impl AppHandler for MaScene {
impl AppHandler for MyScene {
fn setup(&mut self, app: &mut wsg_lib::App) {
app.scene
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
@@ -42,7 +42,7 @@ impl AppHandler for MaScene {
.create_material("mat", "standard", None)
.unwrap();
// Un cube lit par Phong, posé au-dessus d'un plan
// A Phong-lit cube, sitting on a ground plane
app.scene
.create_mesh("cube", cube(1.0), Some("mat"))
.unwrap();
@@ -61,10 +61,10 @@ impl AppHandler for MaScene {
fn main() -> Result<(), WsgError> {
let mut app = AppBuilder::new()
.title("Ma scène WSG")
.with_hdr(ToneMapper::Aces) // optionnel : HDR + tone mapping
.title("My WSG scene")
.with_hdr(ToneMapper::Aces) // optional: HDR + tone mapping
.build()?;
app.run(MaScene);
app.run(MyScene);
Ok(())
}
```
@@ -76,81 +76,91 @@ pollster = { version = "1", features = ["macro"] }
```
```sh
cargo run --example demo # le showcase complet (6 primitives, 3 lumières, ombres, HDR)
cargo run --example demo # full showcase (6 primitives, 3 lights, shadows, HDR)
```
## Fonctionnalités
## Features
| Catégorie | Ce qui est disponible |
|-----------|----------------------|
| **Géométrie** | 6 primitives procédurales + import OBJ + `Geometry` custom |
| **Rendu** | Phong (lit), unlit (2D flat), HDR + tone mapping (ACES/Reinhard) |
| **Lumières** | Directional, point, spot (8 max) + ambient |
| **Ombres** | Shadow mapping (directional/spot), slope-scaled bias, PCF |
| **LOD** | Décimation quadric auto, hystérésis, 1 buffer multi-niveaux |
| Category | What's available |
|----------|-----------------|
| **Geometry** | 6 procedural primitives + OBJ import + custom `Geometry` |
| **Rendering** | Phong (lit), unlit (2D flat), PBR metallic/roughness, HDR + tone mapping |
| **Lights** | Directional, point, spot (8 max) + ambient |
| **Shadows** | Shadow mapping (directional/spot), slope-scaled bias, PCF |
| **LOD** | Auto quadric decimation, hysteresis, 1 buffer multi-level |
| **GPU-driven** | Compute pass (matrices + culling) → indirect draws |
| **Caméra** | Orbitale (drag/zoom/reset) + presets (front/side/top) |
| **Input** | Clavier (pressed/held/released), souris (delta, scroll, boutons) |
| **Textures** | RGBA8 (de bytes, de fichier, placeholder blanc) |
| **Post-process** | Bloom, Depth of Field, Fog (3 modes), MSAA 4× |
| **Camera** | Orbital (drag/zoom/reset) + presets (front/side/top) |
| **Input** | Keyboard (pressed/held/released), mouse (delta, scroll, buttons) |
| **Textures** | RGBA8 (from bytes, from file, white placeholder) |
## Documentation
| Où | Quoi |
|----|------|
| [docs/user/](docs/user/README.md) | **Guide utilisateur** (EN) — comment utiliser l'API, pas à pas |
| [docs/tech/](docs/tech/ARCHI_APP.md) | **Architecture interne** (FR) — décisions, specs, cibles |
| [docs/ROADMAP.md](docs/ROADMAP.md) | Feuille de route (phases 1-5 ✅, phase 6 en cours) |
| [docs/PLAN.md](docs/PLAN.md) | Livre de recette (historique des étapes) |
| `cargo doc -p wsg-lib --no-deps` | **Référence API** (rustdoc, 100 % couvert) |
| Where | What |
|-------|------|
| [docs/user/](docs/user/README.md) | **User guide** — how to use the API, step by step |
| [docs/tech/](docs/tech/ARCHI_APP.md) | **Internal architecture** — decisions, specs, targets |
| [docs/ROADMAP.md](docs/ROADMAP.md) | Roadmap (phases 1-5 ✅, phase 6 in progress) |
| [docs/PLAN.md](docs/PLAN.md) | Recipe book (step history) |
| `cargo doc -p wsg-lib --no-deps` | **API reference** (rustdoc, 100% covered) |
## Exemples
## Examples
| Exemple | Ce qu'il montre |
|---------|----------------|
| `demo` | Le showcase : 6 primitives, 3 lumières, ombres, HDR, LOD, caméra orbitale |
| `cube` | MVP 3D : un cube lit par Phong, texture checkerboard |
| `simple` | Minimal : un quad coloré en mode unlit (2D) |
| `shadow_test` | Ombres portées isolées |
| `spot_test` | Spotlight isolé |
| `import` | Import de fichier OBJ (feature `import-obj`) |
| `manual` | Workflow low-level (Context/Renderer/PipelineCache, sans App) |
| Example | What it shows |
|---------|---------------|
| `demo` | Full showcase: 6 primitives, 3 lights, shadows, HDR, LOD, orbital camera |
| `bloom` | HDR bloom post-process |
| `hdr` | HDR + tone mapping (ACES/Reinhard) |
| `emissive` | Emissive materials + runtime exposure control |
| `shadow` | Shadow mapping in isolation |
| `culling` | GPU-driven frustum culling (15×15 grid) |
| `msaa` | 4× multisample anti-aliasing |
| `fog` | 3 fog modes (linear, exponential, exponential²) |
| `dof` | Depth of field with focus presets |
| `pbr` | PBR metallic/roughness + normal mapping |
| `import` | OBJ file import (feature `import-obj`) |
| `manual` | Low-level workflow (Context/Renderer/PipelineCache, no App) |
## Features Cargo
## Cargo Features
```toml
# Default : toutes les primitives
# Default: all primitives
wsg-lib = { path = "../lib" }
# Minimal : juste le cube
# Minimal: just the cube
wsg-lib = { path = "../lib", default-features = false, features = ["prim-cube"] }
# Avec import OBJ
# With OBJ import
wsg-lib = { path = "../lib", features = ["import-obj"] }
```
| Feature | Active |
|---------|--------|
| Feature | Enables |
|---------|---------|
| `prim-cube`, `prim-plane`, `prim-sphere`, `prim-cylinder`, `prim-cone`, `prim-torus` | Primitives |
| `all-prims` (default) | Les 6 primitives |
| `import-obj` | Parser Wavefront OBJ |
| `all-prims` (default) | All 6 primitives |
| `import-obj` | Wavefront OBJ parser |
| `import-gltf` | glTF (stub) |
## Build
```sh
cargo build --workspace # tout
cargo test --workspace # 116 tests
cargo check --all-targets # vérification rapide
cargo run -p wsg-lib --example demo # lancer le showcase
cargo build --workspace # everything
cargo test --workspace # 127 tests
cargo check --all-targets # quick check
cargo run -p wsg-lib --example demo # run the showcase
```
## Projet
## Project
- **Langage** : Rust 2024
- **Dépendances** : wgpu 30, winit 0.30, glam (math)
- **Pas publié sur crates.io** (dépendance par path)
- **Status** : MVP complet (phases 1-5 ✅), post-MVP en cours (phase 6)
- **Language**: Rust 2024
- **Dependencies**: wgpu 30, winit 0.30, glam (math)
- **Not published on crates.io** (path dependency)
- **Status**: MVP complete (phases 1-5 ✅), post-MVP in progress (phase 6)
---
*Documentation détaillée (architecture, status, API reference, workflow manuel) : [README_DETAILS.md](README_DETAILS.md)*
*Detailed documentation (architecture, status, API reference, manual workflow): [README_DETAILS.md](README_DETAILS.md)*
---
> This project was heavily developed using OpenCode, Pi Code, and JCode AI agents running on local Qwen3-27b_Q4 and DeepSeek V4 Flash Q4 instances. The project organization and architecture are the author's own design.
+53 -36
View File
@@ -1,6 +1,6 @@
# WSG — Documentation détaillée
# WSG — Detailed Documentation
> Contenu technique du README principal : status, architecture, API reference, workflows, roadmap.
> Technical content from the main README: status, architecture, API reference, workflows, roadmap.
## Status
@@ -9,14 +9,19 @@
| Manual workflow (`Context` + `Renderer` + `PipelineCache`) | ✅ Working (advanced — fine-grained control) |
| `App` / `AppBuilder` / `AppHandler` event-loop facade | ✅ Working — window, events, frame presentation, automatic scene rendering |
| `Scene` resource/entity registry | ✅ Working — auto-rendered in one batched pass (`App::render_scene`) |
| GPU-driven two-pass pipeline (Compute → indirect draw) | ✅ Working (Phase 3) — `render_scene` + shadow pass 100 % indirect; opt-in frustum culling |
| GPU-driven two-pass pipeline (Compute → indirect draw) | ✅ Working (Phase 3) — `render_scene` + shadow pass 100% indirect; opt-in frustum culling |
| 3D infrastructure (uniform bind groups, MVP + camera) | ✅ Working — per-frame camera + per-entity world matrices in shared uniforms |
| Shadows (shadow mapping) | ✅ Working — directional/spot, slope-scaled bias, PCF 3×3 |
| HDR + Tone Mapping | ✅ Working (Étape 20) — offscreen Rgba16Float, ACES/Reinhard, opt-in |
| LOD (Level of Detail) | ✅ Working (Étape 19) — quadric decimation, hysteresis, multi-level buffer |
| Mesh module (primitives + import) | ✅ Working (Étape 21) — feature-gated primitives, OBJ parser |
| HDR + Tone Mapping | ✅ Working — offscreen Rgba16Float, ACES/Reinhard, opt-in |
| LOD (Level of Detail) | ✅ Working — quadric decimation, hysteresis, multi-level buffer |
| Mesh module (primitives + import) | ✅ Working — feature-gated primitives, OBJ parser |
| Bloom | ✅ Working — threshold + separable blur + composite, HDR required |
| Fog | ✅ Working — 3 modes (linear, exp, exp²), runtime switchable |
| MSAA | ✅ Working — 4× multisample, resolve pass |
| DoF | ✅ Working — CoC + disc blur, focus presets |
| PBR (metallic/roughness + normal maps) | ✅ Working — Cook-Torrance, GGX, IBL, derivative tangent |
Note: `standard_shader.wgsl` (Phong, with an explicit **unlit** mode) is the **single** shader the library ships. Flat 2D drawing is its unlit variant (`Renderer::set_unlit(true)`).
Note: `standard_shader.wgsl` (Phong + PBR, with an explicit **unlit** mode) is the **single** shader the library ships. Flat 2D drawing is its unlit variant (`Renderer::set_unlit(true)`).
## Architecture
@@ -37,28 +42,31 @@ Spec: [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) · User guide: [d
```
lib/src/
├── lib.rs # crate root, re-exports
├── prelude.rs # glob re-exports (types quotidiens)
├── prelude.rs # glob re-exports
├── app.rs # App + AppBuilder
├── handler.rs # AppHandler trait
├── camera.rs # Camera, CameraController
├── input.rs # InputState
├── lights.rs # Lights, Light, LightType, directional_light, …
├── core/
│ ├── context.rs # GPU lifecycle (Instance/Surface/Adapter/Device/Queue)
│ ├── renderer.rs # RenderPass execution, shadow pass, HDR/TM pass
│ ├── frame.rs # Per-frame RAII (surface texture + view)
│ ├── input.rs # Unified keyboard/mouse state
│ ├── geometry.rs # Geometry (positions/normals/UVs/indices) + BBox
│ ├── transform.rs # Transform (translation/rotation/scale)
│ ├── frustum.rs # Frustum (6 planes, sphere/box culling)
│ ├── lod.rs # LOD decimation (quadric edge collapse)
│ ├── shadow.rs # ShadowConfig (map size, bias, PCF)
│ └── hdr.rs # ToneMapper enum (Aces/Reinhard)
│ ├── hdr.rs # ToneMapper enum (Aces/Reinhard)
│ ├── bloom.rs # BloomConfig + BloomPipeline
│ ├── msaa.rs # MsaaConfig
│ ├── fog.rs # FogConfig + FogMode
│ └── dof.rs # DoFConfig + DoFPipeline
├── mesh/
│ ├── mod.rs # Re-exports flat
│ ├── primitives/ # 6 feature-gated generators
│ └── import/ # OBJ parser + glTF stub
├── pipeline/ # PipelineCache (shader → RenderPipeline)
├── camera/ # Camera, CameraController
├── lights/ # Lights, Light, LightType, directional_light, …
├── input/ # InputState
├── resources/ # Mesh, Material, Texture, Uniform, Vertex
├── scene/ # Scene (registry), Entity
└── utils/ # Conf constants, WsgError
@@ -72,9 +80,9 @@ lib/src/
| AppHandler | Trait | `setup()` / `update()` / `render()` callbacks |
| Scene | Struct | Registry: shaders, materials, meshes, entities, lights, camera |
| Context | Struct | GPU hardware (Instance, Surface, Adapter, Device, Queue) |
| Renderer | Struct | RenderPass execution (scene, shadow, HDR/TM) |
| Renderer | Struct | RenderPass execution (scene, shadow, HDR/TM, bloom, DoF) |
| PipelineCache | Struct | Shader → compiled RenderPipeline cache |
| Material | Struct | Shader ID + texture + pipeline |
| Material | Struct | Shader ID + texture + pipeline + PBR params |
| Geometry | Struct | CPU vertex data (positions/normals/UVs/colors/indices) |
| Mesh / Vertex | Struct | GPU geometry / interleaved upload tuple |
| Frame | Struct | Per-frame RAII (surface texture + view) |
@@ -85,6 +93,10 @@ lib/src/
| Lights / Light | Struct | Light list (directional/point/spot, MAX=8) + ambient |
| ShadowConfig | Struct | Shadow map size, bias, PCF taps, scene radius |
| ToneMapper | Enum | ACES Filmic / Reinhard |
| BloomConfig | Struct | Threshold, intensity, H/V passes |
| MsaaConfig | Struct | Sample count (1 = disabled) |
| FogConfig | Struct | Mode, near/far, density, color |
| DoFConfig | Struct | Focus distance, aperture, max blur |
| BBox | Struct | Axis-aligned bounding box (min/max) |
| Frustum | Struct | 6 planes, sphere/box culling |
@@ -95,14 +107,14 @@ use wsg_lib::prelude::*;
use wsg_lib::app::AppBuilder;
use wsg_lib::utils::WsgError;
struct MaScene;
struct MyScene;
impl AppHandler for MaScene {
impl AppHandler for MyScene {
fn setup(&mut self, app: &mut wsg_lib::App) {
app.scene
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
.unwrap();
app.scene.create_material("mat", "standard", None).unwrap();
app.scene.add_material_shader("mat", "standard").unwrap();
app.scene.create_mesh("cube", cube(1.0), Some("mat")).unwrap();
app.scene.add_entity("my_cube", "cube").unwrap();
}
@@ -112,9 +124,10 @@ impl AppHandler for MaScene {
// render() default: app.render_scene(frame.view()) — auto-draws everything
}
fn main() -> Result<(), WsgError> {
let app = AppBuilder::new().title("WSG").build()?;
app.run(MaScene);
#[pollster::main]
async fn main() -> Result<(), WsgError> {
let app = AppBuilder::new().title("WSG").build().await?;
app.run(MyScene);
Ok(())
}
```
@@ -167,15 +180,15 @@ fn main() {
## Features
| Feature | Default | Fournit |
|---------|---------|---------|
| Feature | Default | Provides |
|---------|---------|----------|
| `prim-cube` | ✅ | `cube(size)` |
| `prim-plane` | ✅ | `plane(w, d, seg_x, seg_z)` |
| `prim-sphere` | ✅ | `uv_sphere(…)`, `icosphere(…)` |
| `prim-cylinder` | ✅ | `cylinder(…)` |
| `prim-cone` | ✅ | `cone(…)` |
| `prim-torus` | ✅ | `torus(…)` |
| `all-prims` | ✅ (default) | Les 6 primitives |
| `all-prims` | ✅ (default) | All 6 primitives |
| `import-obj` | ⬜ | `load_obj(path)`, `parse_obj(str)` |
| `import-gltf` | ⬜ | `load_gltf(path)` (stub) |
@@ -185,6 +198,10 @@ fn main() {
|---------|--------------|----------------|
| Shadows | `scene.set_shadow_caster(Some(idx))` | No shadow map, no depth pass, no PCF |
| HDR + TM | `AppBuilder::with_hdr(ToneMapper::Aces)` | No offscreen texture, no TM pass |
| Bloom | `AppBuilder::with_bloom(BloomConfig::…)` | No bloom textures, no passes |
| MSAA | `AppBuilder::with_msaa(MsaaConfig { sample_count: 4 })` | Single sample, no resolve |
| Fog | `AppBuilder::with_fog(FogConfig::…)` | No fog uniforms |
| DoF | `AppBuilder::with_dof(DoFConfig::…)` | No CoC/blur textures |
| GPU-driven culling | `AppBuilder::with_gpu_driven(true)` | No compute pipeline, no indirect buffers |
| LOD | `scene.create_mesh_with_lod(…, levels)` | Single-level mesh |
| Primitives | Cargo feature `prim-*` | Not compiled |
@@ -194,19 +211,19 @@ fn main() {
| Phase | Status |
|-------|--------|
| 1 — Fondations (window, render loop, Context) | ✅ |
| 2 — Infrastructure 3D (Geometry, Mesh, Material, Pipeline) | ✅ |
| 1 — Foundations (window, render loop, Context) | ✅ |
| 2 — 3D infrastructure (Geometry, Mesh, Material, Pipeline) | ✅ |
| 3 — GPU-driven (compute pass, indirect draws, culling) | ✅ |
| 4 — Rendu avancé (shadows, HDR/TM, lights) | ✅ |
| 5 — Polissage (LOD, camera controller, input, demo) | ✅ |
| 6 — Post-MVP (bloom, PBR, cascaded shadows, SSAO, refactoring) | 🔄 |
| 4 — Advanced rendering (shadows, HDR/TM, lights) | ✅ |
| 5 — Polish (LOD, camera controller, input, demo) | ✅ |
| 6 — Post-MVP (bloom, PBR, fog, DoF, MSAA, cascaded shadows, SSAO) | 🔄 |
## Documentation
| Où | Quoi |
|----|------|
| [docs/user/](docs/user/README.md) | Guide utilisateur (EN) |
| [docs/tech/](docs/tech/ARCHI_APP.md) | Architecture interne (FR) |
| [docs/ROADMAP.md](docs/ROADMAP.md) | Feuille de route |
| [docs/PLAN.md](docs/PLAN.md) | Livre de recette (historique) |
| `cargo doc -p wsg-lib --no-deps` | Référence API (rustdoc) |
| Where | What |
|-------|------|
| [docs/user/](docs/user/README.md) | User guide |
| [docs/tech/](docs/tech/ARCHI_APP.md) | Internal architecture |
| [docs/ROADMAP.md](docs/ROADMAP.md) | Roadmap |
| [docs/PLAN.md](docs/PLAN.md) | Recipe book (step history) |
| `cargo doc -p wsg-lib --no-deps` | API reference (rustdoc) |
+206 -207
View File
@@ -1,341 +1,340 @@
# Exemples WSG
# WSG Examples
Chaque exemple est autonome et illustre **un effet ou une fonctionnalité** spécifique
de la bibliothèque. Tous utilisent l'API déclarative (`AppBuilder` + `AppHandler`).
Each example is self-contained and demonstrates **one effect or feature** of the library. All use the declarative API (`AppBuilder` + `AppHandler`).
## Lancer un exemple
## Running an example
```sh
cargo run -p wsg-lib --example <nom>
cargo run -p wsg-lib --example <name>
```
| Exemple | Effet démontré |
|---------|---------------|
| `demo` | Showcase complet (tous les effets combinés) |
| `bloom` | Post-process bloom (glow autour des zones brillantes) |
| `hdr` | HDR + Tone Mapping (ACES) + contrôle d'exposition |
| `emissive` | Matériaux émissifs (intensités croissantes 0 → 4.0) |
| `shadow` | Shadow mapping (ombre portée directionnelle) |
| `culling` | Culling GPU-driven (grille 15×15, objets hors frustum ignorés) |
| `msaa` | MSAA 4× (anti-aliasing multi-échantillons, arêtes lisses) |
| `fog` | Brouillard de distance (3 modes : linéaire, exp, exp²) |
| `manual` | Workflow bas niveau (Context + Renderer + PipelineCache) |
| `import` | Import de fichier OBJ (non graphique, stdout) |
| Example | Effect demonstrated |
|---------|-------------------|
| `demo` | Full showcase (all effects combined) |
| `bloom` | Post-process bloom (glow around bright areas) |
| `hdr` | HDR + Tone Mapping (ACES) + exposure control |
| `emissive` | Emissive materials (increasing intensities 0 → 4.0) |
| `shadow` | Shadow mapping (directional shadow) |
| `culling` | GPU-driven culling (15×15 grid, off-frustum objects skipped) |
| `msaa` | MSAA 4× (multisample anti-aliasing, smooth edges) |
| `fog` | Distance fog (3 modes: linear, exp, exp²) |
| `dof` | Depth of Field (cinematic bokeh, focus presets) |
| `pbr` | PBR metallic/roughness + normal mapping |
| `manual` | Low-level workflow (Context + Renderer + PipelineCache) |
| `import` | OBJ file import (non-graphical, stdout) |
---
## `demo` — Showcase complet
## `demo` — Full Showcase
Combine **tous** les effets : primitives LOD, textures procédurales, lumières
(directional + point + spot), ombres, HDR/ACES, exposition, émissif, bloom, culling.
Combines **all** effects: LOD primitives, procedural textures, lights
(directional + point + spot), shadows, HDR/ACES, exposure, emissive, bloom, culling.
```sh
cargo run -p wsg-lib --example demo
```
### Touches
### Keys
| Touche | Action |
|--------|--------|
| Glisser (LMB) | Orbiter la caméra |
| Molette | Zoom |
| `R` | Reset caméra |
| `1` / `2` / `3` | Presets : face / côté / dessus |
| `+` / `-` | Exposition ×1.3 / ÷1.3 |
| `0` | Reset exposition |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera |
| Wheel | Zoom |
| `R` | Reset camera |
| `1` / `2` / `3` | Presets: front / side / top |
| `+` / `-` | Exposure ×1.3 / ÷1.3 |
| `0` | Reset exposure |
---
## `bloom` — Post-process Bloom
Deux sphères émissives (orange intensité 2.0, bleue intensité 3.0) produisent un
halo visible. Le cube et le sol servent de référence (non-émissifs).
Two emissive spheres (orange intensity 2.0, blue intensity 3.0) produce a visible
halo. The cube and floor serve as reference (non-emissive).
Le bloom est un pipeline 4 passes GPU : threshold → blur H → blur V → composite.
Bloom is a 4-pass GPU pipeline: threshold → blur H → blur V → composite.
```sh
cargo run -p wsg-lib --example bloom
```
### Touches
### Keys
| Touche | Action |
|--------|--------|
| Glisser (LMB) | Orbiter la caméra |
| Molette | Zoom |
| `R` | Reset caméra |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera |
| Wheel | Zoom |
| `R` | Reset camera |
| `+` / `-` | **Bloom threshold** +0.1 / −0.1 |
| `[` / `]` | **Bloom intensity** +0.1 / −0.1 |
| `I` / `O` | **Bloom radius** +0.5 / −0.5 |
| `E` / `Q` | Exposition ×1.3 / ÷1.3 |
| `0` | Reset exposition |
| `E` / `Q` | Exposure ×1.3 / ÷1.3 |
| `0` | Reset exposure |
### Ce qu'on voit
### What to observe
- **threshold bas** (0.0) : tout l'image "bloom" (effet très diffus).
- **threshold élevé** (2.0+) : seules les sphères émissives brillantes produisent du glow.
- **intensity 0.0** : pas de glow visible (même si le threshold extrait des pixels).
- **radius grand** (10+) : le glow s'étend sur une grande zone.
- **Low threshold** (0.0): the entire image "blooms" (very diffuse effect).
- **High threshold** (2.0+): only the bright emissive spheres produce glow.
- **Intensity 0.0**: no visible glow (even though the threshold extracts pixels).
- **Large radius** (10+): the glow spreads over a large area.
---
## `hdr` — HDR + Tone Mapping
Démontre le rendu HDR avec la courbe ACES Filmic. Trois objets :
Demonstrates HDR rendering with the ACES Filmic curve. Three objects:
- **Cube** : éclairage normal (aucun émissif) — référence LDR.
- **Sphère brillante** (émissif 3.0) : sans HDR, elle serait clampée à blanc.
Avec ACES, les highlights "roulent" doucement vers le blanc (rolloff).
- **Sphère sombre** (émissif 0.3) : reste sombre même à haute exposition.
- **Cube**: normal lighting (no emissive) — LDR reference.
- **Bright sphere** (emissive 3.0): without HDR, it would be clamped to white.
With ACES, highlights "roll off" smoothly toward white.
- **Dark sphere** (emissive 0.3): stays dark even at high exposure.
```sh
cargo run -p wsg-lib --example hdr
```
### Touches
### Keys
| Touche | Action |
|--------|--------|
| Glisser (LMB) | Orbiter la caméra |
| Molette | Zoom |
| `R` | Reset caméra |
| `E` | **Exposition ×1.3** (plus clair) |
| `Q` | **Exposition ÷1.3** (plus sombre) |
| `0` | Reset exposition à 1.0 |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera |
| Wheel | Zoom |
| `R` | Reset camera |
| `E` | **Exposure ×1.3** (brighter) |
| `Q` | **Exposure ÷1.3** (darker) |
| `0` | Reset exposure to 1.0 |
### Ce qu'on voit
### What to observe
- À exposition 1.0 : la sphère brillante est blanche mais avec des détails (rolloff ACES).
- À exposition haute (E×E×E) : la scène s'éclaircit, la sphère brillante reste blanche
(saturée), mais le cube gagne en détail.
- À exposition basse (Q×Q) : tout s'assombrit, la sphère brillante devient orangée
(les valeurs HDR > 1.0 sont compressées).
- At exposure 1.0: the bright sphere is white but with detail (ACES rolloff).
- At high exposure (E×E×E): the scene brightens, the bright sphere stays white
(saturated), but the cube gains detail.
- At low exposure (Q×Q): everything darkens, the bright sphere becomes orange
(HDR values > 1.0 are compressed).
> **Note** : le tone mapper est compilé dans le pipeline au build. Pour comparer
> ACES vs Reinhard, modifier `ToneMapper::Aces` → `ToneMapper::Reinhard` dans le source.
> **Note**: the tone mapper is compiled into the pipeline at build time. To compare
> ACES vs Reinhard, change `ToneMapper::Aces` → `ToneMapper::Reinhard` in the source.
---
## `emissive` — Matériaux Émissifs
## `emissive` — Emissive Materials
Cinq sphères alignées avec des intensités émissives croissantes :
Five spheres in a row with increasing emissive intensities:
| Sphere | Couleur | Intensité | Effet |
|--------|---------|-----------|-------|
| 1 | Gris | 0.0 | Aucune glow (référence) |
| 2 | Orange | 0.5 | Légère lueur |
| 3 | Jaune | 1.0 | Lueur visible |
| 4 | Vert | 2.0 | Glow HDR (au-delà de 1.0) |
| 5 | Bleu | 4.0 | Glow intense (saturation) |
| Sphere | Color | Intensity | Effect |
|--------|-------|-----------|--------|
| 1 | Gray | 0.0 | No glow (reference) |
| 2 | Orange | 0.5 | Slight glow |
| 3 | Yellow | 1.0 | Visible glow |
| 4 | Green | 2.0 | HDR glow (beyond 1.0) |
| 5 | Blue | 4.0 | Intense glow (saturation) |
Avec HDR, les intensités > 1.0 produisent un vrai "glow" (les valeurs dépassent
[0,1] en espace linéaire). Sans HDR, elles seraient clampées à blanc.
With HDR, intensities > 1.0 produce a true "glow" (values exceed
[0,1] in linear space). Without HDR, they would be clamped to white.
```sh
cargo run -p wsg-lib --example emissive
```
### Touches
### Keys
| Touche | Action |
|--------|--------|
| Glisser (LMB) | Orbiter la caméra |
| Molette | Zoom |
| `R` | Reset caméra |
| `E` / `Q` | Exposition ×1.3 / ÷1.3 |
| `0` | Reset exposition |
| `C` | **Cycler le multiplicateur d'émissif** (1× → 2× → 0.5× → ...) |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera |
| Wheel | Zoom |
| `R` | Reset camera |
| `E` / `Q` | Exposure ×1.3 / ÷1.3 |
| `0` | Reset exposure |
| `C` | **Cycle emissive multiplier** (1× → 2× → 0.5× → ...) |
### Ce qu'on voit
### What to observe
- La sphère 1 (intensité 0) est simplement éclairée par la lumière directionnelle.
- Les sphères 2-5 brillent de leur propre lumière, indépendamment de l'éclairage.
- `C` double ou réduit toutes les intensités en même temps (pour voir l'effet HDR).
- Sphere 1 (intensity 0) is simply lit by the directional light.
- Spheres 2-5 glow with their own light, independent of scene lighting.
- `C` doubles or halves all intensities simultaneously (to see the HDR effect).
---
## `shadow` — Shadow Mapping
Quatre objets (cube, sphère, cône, cylindre) sur un sol, éclairés par une lumière
directionnelle qui projette des ombres. La qualité des ombres est contrôlée par
`ShadowConfig` (taille de la shadow map, biais anti-acne).
Four objects (cube, sphere, cone, cylinder) on a floor, lit by a directional light
that casts shadows. Shadow quality is controlled by `ShadowConfig` (map size, anti-acne bias).
```sh
cargo run -p wsg-lib --example shadow
```
### Touches
### Keys
| Touche | Action |
|--------|--------|
| Glisser (LMB) | Orbiter la caméra |
| Molette | Zoom |
| `R` | Reset caméra |
| `1` | Vue de face |
| `2` | Vue de côté |
| `3` | **Vue de dessus** (voir la forme des ombres clairement) |
| `L` | Changer la direction de la lumière (3 presets) |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera |
| Wheel | Zoom |
| `R` | Reset camera |
| `1` | Front view |
| `2` | Side view |
| `3` | **Top view** (see shadow shapes clearly) |
| `L` | Change light direction (3 presets) |
### Ce qu'on voit
### What to observe
- Le cube tourne lentement → son ombre bouge sur le sol.
- La sphère a une transition ombre/lumière douce (terminateur lisse).
- Le cône produit une ombre triangulaire distincte.
- En vue de dessus (`3`), on voit la forme exacte des ombres projetées.
- La taille de la shadow map (1024 par défaut) détermine la résolution :
modifier `SHADOW_MAP_SIZE` en haut du fichier pour tester 256 (pixelisé) ou 2048 (net).
- The cube rotates slowly → its shadow moves on the floor.
- The sphere has a smooth shadow/light transition (soft terminator).
- The cone produces a distinct triangular shadow.
- In top view (`3`), you see the exact shape of projected shadows.
- Shadow map size (1024 default) determines resolution:
modify `SHADOW_MAP_SIZE` at the top of the file to test 256 (pixelated) or 2048 (sharp).
---
## `culling` — GPU Frustum Culling
Une grille de **15×15 = 225 cubes** est placée sur un grand sol. Le culling
GPU-driven (compute shader) détermine quels cubes sont visibles dans le frustum
de la caméra et zéro leurs draw args indirects — **zéro coût CPU**.
A grid of **15×15 = 225 cubes** is placed on a large floor. The GPU-driven culling
(compute shader) determines which cubes are visible in the camera frustum and zeros
their indirect draw args — **zero CPU cost**.
```sh
cargo run -p wsg-lib --example culling
```
### Touches
### Keys
| Touche | Action |
|--------|--------|
| Glisser (LMB) | Orbiter la caméra (regarder autour) |
| Molette | Zoom in/out |
| `R` | Reset (vue de dessus) |
| `1` | Vue de face (les cubes derrière sont culled) |
| `2` | Vue de côté |
| `3` | **Vue de dessus** (voir toute la grille) |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera (look around) |
| Wheel | Zoom in/out |
| `R` | Reset (top view) |
| `1` | Front view (cubes behind are culled) |
| `2` | Side view |
| `3` | **Top view** (see the full grid) |
### Ce qu'on voit
### What to observe
- En vue de dessus (`3`) : toute la grille 20×20 est visible.
- Orbiter à 90° : les cubes derrière la caméra **ne sont pas dessinés** (culled).
- Zoomer très près : seuls les cubes proches du plan de near sont rendus.
- Les cubes tournent lentement (phases décalées) → le culling est dynamique
(un cube peut entrer/sortir du frustum au cours d'une frame).
- In top view (`3`): the entire grid is visible.
- Orbit to 90°: cubes behind the camera **are not drawn** (culled).
- Zoom very close: only cubes near the near plane are rendered.
- Cubes rotate slowly (staggered phases) → culling is dynamic
(a cube can enter/leave the frustum during a frame).
> **Note** : le culling est activé via `AppBuilder::with_culling(true)`. Le modifier
> à `false` dans le source désactive le culling (tous les 400 cubes sont toujours
> dessinés, même hors écran).
> **Note**: culling is enabled via `AppBuilder::with_culling(true)`. Changing
> it to `false` in the source disables culling (all cubes are always drawn, even off-screen).
---
## `msaa` — MSAA 4× (Anti-aliasing)
Démontre l'anti-aliasing multi-échantillons : les arêtes des objets (cube, sphère)
sont lisses au lieu d'être "en escalier". La scène contient un cube (arêtes nettes),
une sphère (silhouette courbe) et un petit cube près de la caméra (aliasing maximal).
Demonstrates multisample anti-aliasing: object edges (cube, sphere)
are smooth instead of "stair-stepped". The scene contains a cube (sharp edges),
a sphere (curved silhouette), and a small cube near the camera (maximum aliasing).
```sh
cargo run -p wsg-lib --example msaa
```
### Touches
### Keys
| Touche | Action |
|--------|--------|
| Glisser (LMB) | Orbiter la caméra |
| Molette | Zoom |
| `R` | Reset caméra |
| `M` | Afficher le nombre d'échantillons |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera |
| Wheel | Zoom |
| `R` | Reset camera |
| `M` | Show sample count |
### Pour comparer avec/sans MSAA
### To compare with/without MSAA
Supprimer la ligne `.with_msaa(4)` dans le source et recompiler : la scène est
identique, seules les arêtes diffèrent (escaler vs lisse).
Remove the `.with_msaa(4)` line in the source and recompile: the scene is
identical, only the edges differ (stair-stepped vs smooth).
> **Note** : MSAA est un réglage de build-time (allocation de textures multi-échantillons).
> Il fonctionne indépendamment de HDR : avec HDR, la texture MSAA est `Rgba16Float`
> et résout dans la texture HDR avant bloom/TM.
> **Note**: MSAA is a build-time setting (multisample texture allocation).
> It works independently of HDR: with HDR, the MSAA texture is `Rgba16Float`
> and resolves into the HDR texture before bloom/TM.
---
## `fog` — Brouillard de distance
## `fog` — Distance Fog
Démontre les 3 modes de brouillard : **linéaire**, **exponentiel**, **exponentiel²**.
La scène contient une rangée de cubes qui s'éloignent et des sphères dispersées sur
un grand plan au sol. Le brouillard fond les objets vers une couleur de fond,
créant l'illusion d'un monde infini.
Demonstrates the 3 fog modes: **linear**, **exponential**, **exponential²**.
The scene contains a row of cubes receding into the distance and scattered spheres
on a large floor plane. Fog blends objects toward a background color,
creating the illusion of an infinite world.
```sh
cargo run -p wsg-lib --example fog --features "all-prims"
```
**Touches** : `1` = linéaire, `2` = exp, `3` = exp², `4` = désactivé, `R` = reset.
**Keys**: `1` = linear, `2` = exp, `3` = exp², `4` = off, `R` = reset.
> Le brouillard est appliqué dans le shader fragment principal (après l'éclairage,
> avant le tone mapping). Il utilise la distance euclidienne du fragment à la caméra.
> Fog is applied in the main fragment shader (after lighting, before tone mapping).
> It uses the Euclidean distance from the fragment to the camera.
---
## `dof` — Depth of Field (bokeh cinématique)
## `dof` — Depth of Field (cinematic bokeh)
Démontre le flou de profondeur de champ : un objet au centre reste net tandis que
le premier et arrière-plan se flouent selon leur distance au plan de mise au point.
Crée un effet d'attention naturelle (type cinématique).
Demonstrates depth of field blur: an object at the focus plane stays sharp while
foreground and background blur according to their distance from the focus plane.
Creates a natural attention effect (cinematic style).
La scène contient un cube de focus au centre, des sphères en premier plan (proches)
et des cubes en arrière-plan (loin), sur un plan au sol.
The scene contains 20 cubes in a row along Z (z=3 to z=-25.5) and 5 spheres to the
sides, on a floor plane. Focus presets at 3m / 8m / 15m.
```sh
cargo run -p wsg-lib --example dof --features "all-prims"
```
**Touches** : `1` = cinématique, `2` = subtil, `3` = focus 2m, `4` = focus 10m, `5` = off, `R` = reset.
**Keys**: `1` = cinematic, `2` = subtle, `3` = focus 3m, `4` = focus 15m, `5` = off, `R` = reset.
> DoF opère en HDR linéaire (après bloom, avant tone mapping). Deux passes :
> CoC (depth → rayon de flou par pixel) puis blur disque 12-taps à rayon variable.
> DoF operates in linear HDR (after bloom, before tone mapping). Two passes:
> CoC (depth → per-pixel blur radius) then 12-tap disc blur with variable radius.
---
## `manual` — Workflow bas niveau
## `pbr` — PBR Metallic/Roughness + Normal Mapping
Démontre l'API **sans** la façade `App` : utilisation directe de `Context`,
`Renderer`, `PipelineCache`, `Mesh`, `Material`. Rend un quad coloré (unlit).
Utile pour comprendre ce que la façade `App` encapsule.
```sh
cargo run -p wsg-lib --example manual
```
Pas de touches — rendu statique (quad unlit, 4 couleurs).
---
## `import` — Import de fichier OBJ
Exemple **non graphique** : parse un fichier `.obj` et affiche les statistiques
(nombre de sommets, normales, UVs, indices, bounding box) sur stdout.
```sh
# Avec un fichier :
cargo run -p wsg-lib --example import --features import-obj -- /path/to/model.obj
# Sans argument (triangle de démonstration) :
cargo run -p wsg-lib --example import --features import-obj
```
Pas de touches — s'exécute et quitte.
---
## `pbr` — PBR Metallic/Roughness + Normal Mapping (Étape 27)
Démonstration du workflow PBR Cook-Torrance : GGX distribution + Smith visibility +
Schlick Fresnel + IBL hémisphérique + normal mapping.
Demonstrates the Cook-Torrance PBR workflow: GGX distribution + Smith geometry +
Schlick Fresnel + hemispheric IBL + normal mapping.
```sh
cargo run -p wsg-lib --example pbr
```
| Touche | Action |
|--------|--------|
| Drag (LMB) | Orbite caméra |
| Molette | Zoom |
| `R` | Reset caméra |
| Key | Action |
|-----|--------|
| Drag (LMB) | Orbit camera |
| Wheel | Zoom |
| `R` | Reset camera |
Scène : 6 matériaux PBR (métal miroir, plastique, rouillé, céramique, bump map, sol matte).
Le cube avec normal map montre des bumps procéduraux (sin wave).
Scene: 6 PBR materials (mirror metal, smooth plastic, rusty metal, ceramic, bump map, matte floor).
The bump-map cube shows procedural sin-wave surface detail.
---
## `manual` — Low-level Workflow
Demonstrates the API **without** the `App` facade: direct use of `Context`,
`Renderer`, `PipelineCache`, `Mesh`, `Material`. Renders a colored quad (unlit).
Useful for understanding what the `App` facade encapsulates.
```sh
cargo run -p wsg-lib --example manual
```
No keys — static render (unlit quad, 4 colors).
---
## `import` — OBJ File Import
**Non-graphical** example: parses a `.obj` file and prints statistics
(vertex count, normals, UVs, indices, bounding box) to stdout.
```sh
# With a file:
cargo run -p wsg-lib --example import --features import-obj -- /path/to/model.obj
# Without argument (demo triangle):
cargo run -p wsg-lib --example import --features import-obj
```
No keys — runs and exits.