diff --git a/AGENTS.md b/AGENTS.md index 87a9484..73c7796 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,22 +5,24 @@ Rust workspace (2024 edition) wrapping [wgpu](https://github.com/gfx-rs/wgpu) fo ## Workspace Structure ``` -Cargo.toml # workspace root — no dependencies here -lib/Cargo.toml # wsg-lib crate: wgpu 30.0.0, winit 0.30 -examples/Cargo.toml # depends on wsg-lib via path reference -lib/lib.rs # lib entry point -lib/context.rs # Context type (aggregates wgpu objects: Instance, Surface, Adapter, Device, Queue) -lib/renderer.rs # renderer implementation -examples/src/main.rs # example binary +Cargo.toml # workspace root (members = ["lib"]) — no dependencies here +lib/Cargo.toml # wsg-lib crate: wgpu 30.0.0, winit 0.30 + explicit [[example]] entries +lib/src/ # library source (app, core/, mesh/, pipeline/, resources/, scene/, utils/) +lib/examples/ # examples, one subfolder per category (each folder has a README.md): +│ ├── meshes/ # simple, cube, pbr, import, manual +│ ├── lights/ # shadow, shadow_test, spot_test, emissive +│ ├── cameras/ # culling +│ └── effects/ # demo, bloom, hdr, msaa, fog, dof ``` -**Key convention**: `wsg-lib` is referenced from `examples/` via relative path (`path = "../lib"`). Do not publish this to crates.io as-is — it uses a local path dependency. +**Key convention**: examples live in `lib/examples//` subfolders. Cargo only auto-discovers top-level `examples/*.rs`, so **every example is declared explicitly in `lib/Cargo.toml`** (`[[example]] name = … path = "examples//….rs"`). Names are stable: `cargo run -p wsg-lib --example ` works as before. Do not publish this to crates.io as-is — it uses local path conventions. ## Essential Commands | Action | Command | |--------|---------| | Build everything | `cargo build --workspace` | -| Run examples | `cargo run -p examples` | +| Run an example | `cargo run -p wsg-lib --example ` | +| Run a feature-gated example | `cargo run -p wsg-lib --example import --features import-obj` | | Test | `cargo test --workspace` | | Check | `cargo check --workspace` | | Format | `cargo fmt --all` | @@ -41,7 +43,7 @@ WGPU doesn't have a native "Context" object — this type groups them together f ## Gotchas - Rust 2024 edition is used. Ensure your Rust toolchain supports it (`rustup update`). - wgpu 30.0.0 is pinned in `lib/Cargo.toml`. The comment says "check the latest version" — verify compatibility before upgrading. -- No feature flags, no dev-dependencies, no tests yet. Adding any requires updating both `Cargo.toml` files if the dependency spans crates. +- Cargo features gate primitives (`prim-*`, `all-prims` is default) and importers (`import-obj`, `import-gltf`); the `import` example is `required-features = ["import-obj"]`. 127 tests exist (`cargo test --workspace`). - The workspace has no `[workspace.dependencies]` section. Dependencies are declared per-crate rather than centrally. - **WGSL `select` argument order** (cost us a day): `select(reject, accept, cond)` returns the **second** arg when `cond` is true — the reverse of HLSL's `select(trueVal, falseVal, cond)`. In `shaders/gpu_driven.wgsl` the cull pass must stay `select(0u, u32(flags.z), visible)` (visible ⇒ full count, culled ⇒ 0). Swapped args silently zero the counts of every visible entity → black window. See the GOTCHA comment at the top of that shader. - **LOD UV blending: never fold integer-tile jumps, freeze seam twins instead** (cost us a day, 2026-09-23): a UV *seam* is two copies of the same 3-D point on integer-apart UVs (u=0/u=1 columns) — it is NOT a mesh edge, so the decimation must record the weld's refused pairs and **freeze** those twins (any edge touching one is excluded from the PQ). A co-facial edge spanning a whole tile (cone apex v=1 ↔ base v=0) is a *legit* chart span — the chart is bilinear, so the UVs **blend linearly** (fold the integer jump to zero and the apex UV smears down the cone side). And the attribute-aware weld refuses a Δ of *exactly* 0.5 (ambiguous: seam at its widest vs legit half-tile jump — the cone's u=1 column vs the cap-disc chart sits exactly there). See the comments in `geometry.rs` (`welded`, `Collapse::collapse_edge`) and the cone/seam regression tests. diff --git a/README.md b/README.md index 82171da..44edec9 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ pollster = { version = "1", features = ["macro"] } ``` ```sh -cargo run --example demo # full showcase (6 primitives, 3 lights, shadows, HDR) +cargo run -p wsg-lib --example demo # full showcase (6 primitives, 3 lights, shadows, HDR) ``` ## Features @@ -100,26 +100,37 @@ cargo run --example demo # full showcase (6 primitives, 3 lights, shadows, HDR |-------|------| | [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 | +| [lib/examples/](lib/examples/README.md) | **Examples** — 4 category folders (meshes/lights/cameras/effects), each with a README | | [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) | ## Examples -| 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) | +Sixteen examples in `lib/examples/`, organized into **four category folders** — +each folder has its own README (run commands, keys, what to observe): +[`lib/examples/README.md`](lib/examples/README.md). All run from the repo root +with `cargo run -p wsg-lib --example ` (feature-gated ones need +`--features`, e.g. `import` → `--features import-obj`). + +| Folder | Example | What it shows | +|--------|---------|---------------| +| [`meshes/`](lib/examples/meshes/README.md) | `simple` | Minimal declarative workflow (flat unlit quad, ~15 lines) | +| | `cube` | Textured, lit, spinning cube (the 3D MVP) | +| | `pbr` | PBR metallic/roughness + normal mapping | +| | `import` | OBJ file import (feature `import-obj`) | +| | `manual` | Low-level workflow (Context/Renderer/PipelineCache, no App) | +| [`lights/`](lib/examples/lights/README.md) | `shadow` | Shadow mapping in isolation | +| | `shadow_test` | Dedicated shadow test (directional caster + PCF) | +| | `spot_test` | Isolated spot light (beam, penumbra) | +| | `emissive` | Emissive materials + runtime exposure control | +| [`cameras/`](lib/examples/cameras/README.md) | `culling` | GPU-driven frustum culling (15×15 grid) | +| [`effects/`](lib/examples/effects/README.md) | `demo` | Full showcase: 6 primitives, 3 lights, shadows, HDR, LOD, orbital camera | +| | `bloom` | HDR bloom post-process | +| | `hdr` | HDR + tone mapping (ACES/Reinhard) | +| | `msaa` | 4× multisample anti-aliasing | +| | `fog` | 3 fog modes (linear, exponential, exponential²) | +| | `dof` | Depth of field with focus presets | ## Cargo Features diff --git a/docs/user/README.md b/docs/user/README.md index 9742db5..6af6b4c 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -27,7 +27,7 @@ GPU graphics background is required. | [DoF (depth of field)](dof.md) | Cinematic bokeh blur, focus plane, opt-in via `with_dof()` | | [GPU-driven rendering](gpu-driven.md) | GPU world matrices + indirect draws, opt-in frustum culling | | [Camera & input](camera-input.md) | Active camera, orbital controller, unified keyboard/mouse state | -| [Examples](examples.md) | The 7 repo examples, the advanced `manual` workflow, adding your own example | +| [Examples](examples.md) | The 16 repo examples in 4 folders (`meshes/`, `lights/`, `cameras/`, `effects/`), the advanced `manual` workflow, adding your own example | The pages are cross-linked: each page ends with a link to the next one. diff --git a/docs/user/camera-input.md b/docs/user/camera-input.md index 3b93351..eaba190 100644 --- a/docs/user/camera-input.md +++ b/docs/user/camera-input.md @@ -57,7 +57,7 @@ Two public fields tune the feel of the camera (defaults in parentheses): | `zoom_factor` | multiplicative distance change per wheel notch (`distance *= factor^scroll`) | `0.9` (10% per notch) | The exact wiring snippet (orbit + zoom + reset + `1`/`2`/`3` presets, driven from -`app.input`) is in [`demo.rs`](../../lib/examples/demo.rs), `update()` section. +`app.input`) is in [`demo.rs`](../../lib/examples/effects/demo.rs), `update()` section. ## 3. The unified input state diff --git a/docs/user/examples.md b/docs/user/examples.md index 75c0682..2459e4e 100644 --- a/docs/user/examples.md +++ b/docs/user/examples.md @@ -1,17 +1,33 @@ # Examples -Seven examples live in [`lib/examples/`](../../lib/examples/) and all launch with -`cargo run -p wsg-lib --example `. They are **self-contained**: no assets on disk -(procedural textures, hard-coded geometries). +Sixteen examples live in [`lib/examples/`](../../lib/examples/), organized into +**four category folders** — [`meshes/`](../../lib/examples/meshes/README.md), +[`lights/`](../../lib/examples/lights/README.md), +[`cameras/`](../../lib/examples/cameras/README.md), +[`effects/`](../../lib/examples/effects/README.md) — each folder with its own +`README.md` (per-example details: keys, what to observe). All launch with +`cargo run -p wsg-lib --example ` (names are stable, run from the repo +root). They are **self-contained**: no assets on disk (procedural textures, +hard-coded geometries). -| Example | Command | What it shows | Corresponding page | -|---------|----------|---------------|--------------------| -| `simple` | `cargo run -p wsg-lib --example simple` | The minimal declarative workflow: a two-tone 2D quad, **unlit**, rendered automatically. The "15 lines, no wgpu" model | [Quickstart](quickstart.md), [Materials](materials.md) (§ unlit) | -| `cube` | `cargo run -p wsg-lib --example cube` | The 3D MVP: a textured (checkerboard) cube, lit (directional + point + spot), spinning | [Meshes](meshes.md), [Materials](materials.md), [Lights](lights.md) | -| `demo` | `cargo run -p wsg-lib --example demo` | The full showcase: ground + 6 primitives, textures, 3 lights, **shadows**, **orbital camera** on keyboard/mouse (left-drag = orbit, wheel = zoom, `R` = reset, `1`/`2`/`3` = presets) | [All pages](README.md) | -| `shadow_test` | `cargo run -p wsg-lib --example shadow_test` | Isolated shadow mapping: a cube casts a PCF-softened shadow on the ground (`clear_lights` technique → caster at index 0) | [Shadows](shadows.md) | -| `spot_test` | `cargo run -p wsg-lib --example spot_test` | Isolated spot (ambient nearly zero): the directed beam, the penumbra, the attenuation | [Lights](lights.md) | -| `manual` | `cargo run -p wsg-lib --example manual` | The **advanced** workflow: `Context`/`Renderer`/`PipelineCache` driven by hand, without the `App` facade (winit 0.30 `ApplicationHandler`) | below | +| Folder | Example | What it shows | Corresponding page | +|--------|---------|---------------|--------------------| +| `meshes/` | `simple` | The minimal declarative workflow: a two-tone 2D quad, **unlit**, rendered automatically. The "15 lines, no wgpu" model | [Quickstart](quickstart.md), [Materials](materials.md) (§ unlit) | +| `meshes/` | `cube` | The 3D MVP: a textured (checkerboard) cube, lit (directional + point + spot), spinning | [Meshes](meshes.md), [Materials](materials.md), [Lights](lights.md) | +| `meshes/` | `pbr` | PBR metallic/roughness + normal mapping (6 materials) | [Materials](materials.md) | +| `meshes/` | `import` | OBJ file import (non-graphical, prints stats to stdout) | [Meshes](mesh.md) | +| `meshes/` | `manual` | The **advanced** workflow: `Context`/`Renderer`/`PipelineCache` driven by hand, without the `App` facade | below | +| `lights/` | `shadow` | Shadow mapping in isolation (4 objects on a floor) | [Shadows](shadows.md) | +| `lights/` | `shadow_test` | Isolated shadow mapping: a cube casts a PCF-softened shadow on the ground (`clear_lights` technique → caster at index 0) | [Shadows](shadows.md) | +| `lights/` | `spot_test` | Isolated spot (ambient nearly zero): the directed beam, the penumbra, the attenuation | [Lights](lights.md) | +| `lights/` | `emissive` | Emissive materials (intensities 0 → 4.0) + runtime exposure | [Emissive & exposure](emissive-exposure.md) | +| `cameras/` | `culling` | GPU-driven frustum culling: 15×15 grid, off-frustum cubes skipped (zero CPU cost) | [GPU-driven](gpu-driven.md) | +| `effects/` | `demo` | The full showcase: ground + 6 LOD primitives, textures, 3 lights, **shadows**, **orbital camera** on keyboard/mouse, HDR/ACES, bloom | [All pages](README.md) | +| `effects/` | `bloom` | Post-process bloom (threshold → blur → composite) | [Bloom](bloom.md) | +| `effects/` | `hdr` | HDR + tone mapping (ACES) + runtime exposure control | [HDR](hdr.md) | +| `effects/` | `msaa` | MSAA 4× (smooth edges vs stair-stepped) | [MSAA](msaa.md) | +| `effects/` | `fog` | Distance fog (3 modes: linear, exp, exp²) | [Fog](fog.md) | +| `effects/` | `dof` | Depth of field (cinematic bokeh, focus presets) | [effects README](../../lib/examples/effects/README.md) | ## The `manual` workflow (advanced) @@ -26,7 +42,7 @@ framework, experimentation), you bypass `App` and drive directly: The window and GPU are created in winit 0.30's `resumed()` callback (`run_app` + `ApplicationHandler`), as in `app.rs`. The reference file is -[`manual.rs`](../../lib/examples/manual.rs); the two-layer architecture is detailed in +[`manual.rs`](../../lib/examples/meshes/manual.rs); the two-layer architecture is detailed in [ARCHI_APP](../tech/ARCHI_APP.md) and [FRAME_LOOP](../tech/FRAME_LOOP.md). > **Tip**: start with the declarative workflow. The manual workflow doesn't render more @@ -34,12 +50,19 @@ The window and GPU are created in winit 0.30's `resumed()` callback (`run_app` + ## Adding your own example -Repo conventions (see `lib/examples/README.md`): +Repo conventions (see [`lib/examples/README.md`](../../lib/examples/README.md)): -1. Create `lib/examples/my_example.rs` (Cargo discovers it automatically). -2. Keep it **self-contained**: procedural textures, hard-coded geometries, no external assets. -3. Use the declarative workflow (`AppBuilder` + `Scene`) when possible. -4. Document the example in `lib/examples/README.md` (and here, `docs/user/examples.md`). +1. Create `lib/examples//my_example.rs` (pick the matching category — + `meshes/`, `lights/`, `cameras/`, `effects/` — or add a new folder + README). +2. Declare it in `lib/Cargo.toml` — examples live in subfolders, so Cargo does + **not** discover them automatically: + ```toml + [[example]] + name = "my_example" + path = "examples//my_example.rs" + ``` +3. Keep it **self-contained**: procedural textures, hard-coded geometries, no external assets. +4. Document the example in the folder's `README.md` (and here, `docs/user/examples.md`). ## Links diff --git a/docs/user/fog.md b/docs/user/fog.md index 231ed56..f5b7e3a 100644 --- a/docs/user/fog.md +++ b/docs/user/fog.md @@ -125,7 +125,7 @@ brouillé. Résultat : le brouillard est perceptuellement cohérent. ## Exemple -Voir `examples/fog.rs` : 15 cubes en rangée + 5 sphères sur un plan 80×80, +Voir `lib/examples/effects/fog.rs` : 15 cubes en rangée + 5 sphères sur un plan 80×80, avec commutation runtime entre les 3 modes. ```sh diff --git a/docs/user/lights.md b/docs/user/lights.md index 23745ce..c67a928 100644 --- a/docs/user/lights.md +++ b/docs/user/lights.md @@ -37,9 +37,9 @@ app.scene.add_spot_light( ).unwrap(); ``` -These three calls are the ones in the [`demo`](../../lib/examples/demo.rs) example; -[`cube.rs`](../../lib/examples/cube.rs) shows a point + a spot on top of the default -directional, and [`spot_test.rs`](../../lib/examples/spot_test.rs) isolates a single spot +These three calls are the ones in the [`demo`](../../lib/examples/effects/demo.rs) example; +[`cube.rs`](../../lib/examples/meshes/cube.rs) shows a point + a spot on top of the default +directional, and [`spot_test.rs`](../../lib/examples/lights/spot_test.rs) isolates a single spot (ambient nearly zero). Global settings: @@ -68,7 +68,7 @@ Two consequences: [Shadows](shadows.md). 2. If you want **your** light to be the only one (and thus at index 0), clear the list first: `app.scene.clear_lights();` then `add_*_light(…)` (this is the technique in - [`shadow_test.rs`](../../lib/examples/shadow_test.rs)). + [`shadow_test.rs`](../../lib/examples/lights/shadow_test.rs)). ## Intensities and tints diff --git a/docs/user/materials.md b/docs/user/materials.md index 86be5e0..28d60bb 100644 --- a/docs/user/materials.md +++ b/docs/user/materials.md @@ -43,7 +43,7 @@ app.scene.create_mesh("cube_mesh", cube(1.0), Some("mat_textured")).unwrap(); A mesh created with `material = None` is rendered with the scene's **default material** (`standard`, built once then cached) — that is the behavior of the -[`simple`](../../lib/examples/simple.rs) example. +[`simple`](../../lib/examples/meshes/simple.rs) example. ## 3. Diffuse textures @@ -70,7 +70,7 @@ app.scene.add_material_texture("ground_mat", "standard", "checker_texture").unwr ``` The exact snippet (8×8 checkerboard + stripes generation) is in -[`demo.rs`](../../lib/examples/demo.rs) and [`cube.rs`](../../lib/examples/cube.rs). +[`demo.rs`](../../lib/examples/effects/demo.rs) and [`cube.rs`](../../lib/examples/meshes/cube.rs). Two conditions for a texture to show up: 1. the material is created via `add_material_texture` (otherwise the 1×1 white placeholder diff --git a/docs/user/meshes.md b/docs/user/meshes.md index a52260d..1f9cf90 100644 --- a/docs/user/meshes.md +++ b/docs/user/meshes.md @@ -86,7 +86,7 @@ horizon — see [ROADMAP](../ROADMAP.md)). Placement lives on the **entity** (not on the mesh): `Transform { translation: Vec3, rotation: Quat, scale: Vec3 }`, converted to a world matrix by the engine every frame. -The snippet below is the animation from the [`cube`](../../lib/examples/cube.rs) example: +The snippet below is the animation from the [`cube`](../../lib/examples/meshes/cube.rs) example: ```rust fn update(&mut self, app: &mut wsg_lib::App) { diff --git a/docs/user/msaa.md b/docs/user/msaa.md index fa2f7ed..1207307 100644 --- a/docs/user/msaa.md +++ b/docs/user/msaa.md @@ -123,5 +123,5 @@ let app = AppBuilder::new() .await?; ``` -See `examples/msaa.rs` for a full interactive demo with cube, sphere, +See `lib/examples/effects/msaa.rs` for a full interactive demo with cube, sphere, and ground plane where aliasing is clearly visible without MSAA. diff --git a/docs/user/quickstart.md b/docs/user/quickstart.md index da3f369..4adb955 100644 --- a/docs/user/quickstart.md +++ b/docs/user/quickstart.md @@ -22,7 +22,7 @@ pollster = { version = "1", features = ["macro"] } # for #[pollster::main] (Ap ## 2. The minimal application -This snippet is the [`simple`](../../lib/examples/simple.rs) example from the repo, almost +This snippet is the [`simple`](../../lib/examples/meshes/simple.rs) example from the repo, almost verbatim: a flat two-tone quad, rendered automatically every frame. ```rust @@ -107,7 +107,8 @@ Golden rule: **mutate the scene in `update()`** (and `setup()`), only read it in ## 4. Running it -From the WSG repo root (the examples live in `lib/examples/`): +From the WSG repo root (the examples live in `lib/examples/`, one folder per +category: `meshes/`, `lights/`, `cameras/`, `effects/`): | Command | What you see | |----------|--------------| diff --git a/docs/user/shadows.md b/docs/user/shadows.md index 7098744..8b8f25f 100644 --- a/docs/user/shadows.md +++ b/docs/user/shadows.md @@ -29,7 +29,7 @@ Two ways to avoid it: app.scene.set_shadow_caster(Some(0)); // now it really is YOUR light ``` - This is the technique in [`shadow_test.rs`](../../lib/examples/shadow_test.rs). + This is the technique in [`shadow_test.rs`](../../lib/examples/lights/shadow_test.rs). 2. **Count the indices** — if you keep the default light and add yours, it lands at index 1: @@ -38,7 +38,7 @@ Two ways to avoid it: app.scene.set_shadow_caster(Some(1)); // this is the demo's warm light that casts ``` - This is the technique in [`demo.rs`](../../lib/examples/demo.rs). + This is the technique in [`demo.rs`](../../lib/examples/effects/demo.rs). ## How it works (to understand the limits) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index c935560..4215daa 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -33,6 +33,77 @@ pollster = { version="1.0.1", features = ["macro"] } # default-features = false pour n'emporter que les codecs utiles (plus petit arbre de compilation). image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } +# Examples live in per-category subfolders (meshes/, lights/, cameras/, effects/). +# Cargo only auto-discovers top-level `examples/*.rs`, so every example is +# declared explicitly with its `path`. Names are stable: `cargo run -p wsg-lib +# --example ` works exactly as before the reorganization. +# Each folder has a README.md documenting its examples. + +# --- meshes/ : geometry, materials, import, low-level workflow --- +[[example]] +name = "simple" +path = "examples/meshes/simple.rs" + +[[example]] +name = "cube" +path = "examples/meshes/cube.rs" + +[[example]] +name = "pbr" +path = "examples/meshes/pbr.rs" + [[example]] name = "import" +path = "examples/meshes/import.rs" required-features = ["import-obj"] + +[[example]] +name = "manual" +path = "examples/meshes/manual.rs" + +# --- lights/ : shadow mapping, spot, emissive --- +[[example]] +name = "shadow" +path = "examples/lights/shadow.rs" + +[[example]] +name = "shadow_test" +path = "examples/lights/shadow_test.rs" + +[[example]] +name = "spot_test" +path = "examples/lights/spot_test.rs" + +[[example]] +name = "emissive" +path = "examples/lights/emissive.rs" + +# --- cameras/ : camera-driven rendering (frustum culling) --- +[[example]] +name = "culling" +path = "examples/cameras/culling.rs" + +# --- effects/ : HDR, post-process, the full showcase --- +[[example]] +name = "demo" +path = "examples/effects/demo.rs" + +[[example]] +name = "bloom" +path = "examples/effects/bloom.rs" + +[[example]] +name = "hdr" +path = "examples/effects/hdr.rs" + +[[example]] +name = "msaa" +path = "examples/effects/msaa.rs" + +[[example]] +name = "fog" +path = "examples/effects/fog.rs" + +[[example]] +name = "dof" +path = "examples/effects/dof.rs" diff --git a/lib/examples/README.md b/lib/examples/README.md index 7d645c3..8b7b3fa 100644 --- a/lib/examples/README.md +++ b/lib/examples/README.md @@ -1,340 +1,61 @@ # WSG Examples -Each example is self-contained and demonstrates **one effect or feature** of the library. All use the declarative API (`AppBuilder` + `AppHandler`). +The examples are organized into **four category folders**, one per theme. Each +folder has its own `README.md` documenting its examples in detail (what they +demonstrate, how to run them, keyboard controls, what to observe). + +| Folder | Theme | Examples | +|--------|-------|----------| +| [meshes/](meshes/README.md) | Geometry, materials, file import, low-level workflow | `simple`, `cube`, `pbr`, `import`, `manual` | +| [lights/](lights/README.md) | Light types, shadow mapping, emissive materials | `shadow`, `shadow_test`, `spot_test`, `emissive` | +| [cameras/](cameras/README.md) | Camera-driven rendering (frustum culling) | `culling` | +| [effects/](effects/README.md) | HDR, tone mapping, post-process, full showcase | `demo`, `bloom`, `hdr`, `msaa`, `fog`, `dof` | ## Running an example +Example **names are stable** — from the repo root: + ```sh cargo run -p wsg-lib --example ``` -| 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` — Full Showcase - -Combines **all** effects: LOD primitives, procedural textures, lights -(directional + point + spot), shadows, HDR/ACES, exposure, emissive, bloom, culling. +Examples gated behind a Cargo feature need the feature too: ```sh -cargo run -p wsg-lib --example demo -``` - -### Keys - -| 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 - -Two emissive spheres (orange intensity 2.0, blue intensity 3.0) produce a visible -halo. The cube and floor serve as reference (non-emissive). - -Bloom is a 4-pass GPU pipeline: threshold → blur H → blur V → composite. - -```sh -cargo run -p wsg-lib --example bloom -``` - -### Keys - -| 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` | Exposure ×1.3 / ÷1.3 | -| `0` | Reset exposure | - -### What to observe - -- **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 - -Demonstrates HDR rendering with the ACES Filmic curve. Three objects: - -- **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 -``` - -### Keys - -| 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 | - -### What to observe - -- 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**: 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` — Emissive Materials - -Five spheres in a row with increasing emissive intensities: - -| 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) | - -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 -``` - -### Keys - -| 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× → ...) | - -### What to observe - -- 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 - -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 -``` - -### Keys - -| 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) | - -### What to observe - -- 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 - -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 -``` - -### Keys - -| 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) | - -### What to observe - -- 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**: 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) - -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 -``` - -### Keys - -| Key | Action | -|-----|--------| -| Drag (LMB) | Orbit camera | -| Wheel | Zoom | -| `R` | Reset camera | -| `M` | Show sample count | - -### To compare with/without MSAA - -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 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` — Distance Fog - -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" -``` - -**Keys**: `1` = linear, `2` = exp, `3` = exp², `4` = off, `R` = reset. - -> 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 (cinematic bokeh) - -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). - -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" -``` - -**Keys**: `1` = cinematic, `2` = subtle, `3` = focus 3m, `4` = focus 15m, `5` = off, `R` = reset. - -> 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. - ---- - -## `pbr` — PBR Metallic/Roughness + 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 -``` - -| Key | Action | -|-----|--------| -| Drag (LMB) | Orbit camera | -| Wheel | Zoom | -| `R` | Reset camera | - -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. +All examples are **self-contained**: procedural textures, hard-coded geometries, +no on-disk assets. All use the declarative API (`AppBuilder` + `AppHandler`) +except `manual`, which demonstrates the low-level workflow instead. + +> **Where do the files live?** Examples live in subfolders +> (`examples//.rs`). Cargo only auto-discovers top-level +> `examples/*.rs`, so every example is declared explicitly in +> [`lib/Cargo.toml`](../Cargo.toml) with its `path`. This keeps +> `--example ` working while allowing the folder organization. + +## Suggested learning path + +1. `simple` — the minimal declarative workflow (flat unlit quad, ~15 lines) +2. `cube` — the 3D MVP: a textured, lit, spinning cube +3. `pbr` — PBR materials and normal mapping +4. `spot_test`, `shadow_test` — isolated light and shadow behavior +5. `hdr` → `emissive` → `bloom` — the HDR chain, step by step +6. `culling` — GPU-driven frustum culling +7. `demo` — everything combined +8. `manual` — what the `App` facade actually encapsulates + +## Adding your own example + +1. Create `lib/examples//my_example.rs` (pick the matching category; + add a new folder + README if needed). +2. Declare it in `lib/Cargo.toml` (Cargo won't discover it otherwise): + ```toml + [[example]] + name = "my_example" + path = "examples//my_example.rs" + ``` +3. Keep it **self-contained**: procedural textures, hard-coded geometries, no + external assets. +4. Document it in the folder's `README.md` (and in `docs/user/examples.md`). diff --git a/lib/examples/cameras/README.md b/lib/examples/cameras/README.md new file mode 100644 index 0000000..8361147 --- /dev/null +++ b/lib/examples/cameras/README.md @@ -0,0 +1,52 @@ +# Cameras & Camera-Driven Rendering + +Examples where the **camera** drives what gets rendered. + +| Example | Run command | What it shows | +|---------|-------------|---------------| +| `culling` | `cargo run -p wsg-lib --example culling` | GPU-driven frustum culling: a 15×15 grid of cubes, off-frustum objects skipped | + +> All commands run from the repo root. + +The frustum is defined by the camera's view-projection matrix, so frustum +culling is inherently a camera concept: move the camera and the set of drawn +objects changes — with **zero CPU cost** (the GPU decides in a compute pass). + +--- + +## `culling` — GPU Frustum Culling + +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 +``` + +### Keys + +| 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) | + +### What to observe + +- 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**: 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). +> +> The GPU-driven pipeline (compute matrices → culling → indirect draws) is +> documented in [`docs/tech/ARCHI_CPU_GPU.md`](../../docs/tech/ARCHI_CPU_GPU.md) +> and [`docs/user/gpu-driven.md`](../../docs/user/gpu-driven.md). diff --git a/lib/examples/culling.rs b/lib/examples/cameras/culling.rs similarity index 100% rename from lib/examples/culling.rs rename to lib/examples/cameras/culling.rs diff --git a/lib/examples/effects/README.md b/lib/examples/effects/README.md new file mode 100644 index 0000000..9478c2f --- /dev/null +++ b/lib/examples/effects/README.md @@ -0,0 +1,178 @@ +# Effects: HDR, Post-process & Showcase + +Examples covering **HDR / tone mapping** and **post-process effects**, plus +the full showcase that combines everything. + +| Example | Run command | What it shows | +|---------|-------------|---------------| +| `demo` | `cargo run -p wsg-lib --example demo` | **Full showcase**: 6 LOD primitives, 3 lights, shadows, HDR/ACES, bloom, culling, orbital camera | +| `bloom` | `cargo run -p wsg-lib --example bloom` | Post-process bloom (glow around bright areas) | +| `hdr` | `cargo run -p wsg-lib --example hdr` | HDR + tone mapping (ACES) + runtime exposure control | +| `msaa` | `cargo run -p wsg-lib --example msaa` | MSAA 4× (multisample anti-aliasing, smooth edges) | +| `fog` | `cargo run -p wsg-lib --example fog --features "all-prims"` | Distance fog (3 modes: linear, exp, exp²) | +| `dof` | `cargo run -p wsg-lib --example dof --features "all-prims"` | Depth of field (cinematic bokeh, focus presets) | + +> All commands run from the repo root. All effects are **opt-in** — a disabled +> effect allocates nothing and executes nothing. + +--- + +## `demo` — Full Showcase + +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 +``` + +### Keys + +| 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 + +Two emissive spheres (orange intensity 2.0, blue intensity 3.0) produce a +visible halo. The cube and floor serve as reference (non-emissive). + +Bloom is a 4-pass GPU pipeline: threshold → blur H → blur V → composite. + +```sh +cargo run -p wsg-lib --example bloom +``` + +### Keys + +| 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` | Exposure ×1.3 / ÷1.3 | +| `0` | Reset exposure | + +### What to observe + +- **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 + +Demonstrates HDR rendering with the ACES Filmic curve. Three objects: + +- **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 +``` + +### Keys + +| 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 | + +### What to observe + +- 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**: the tone mapper is compiled into the pipeline at build time. To +> compare ACES vs Reinhard, change `ToneMapper::Aces` → `ToneMapper::Reinhard` +> in the source. + +--- + +## `msaa` — MSAA 4× (Anti-aliasing) + +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 +``` + +### Keys + +| Key | Action | +|-----|--------| +| Drag (LMB) | Orbit camera | +| Wheel | Zoom | +| `R` | Reset camera | +| `M` | Show sample count | + +### To compare with/without MSAA + +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 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` — Distance Fog + +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" +``` + +**Keys**: `1` = linear, `2` = exp, `3` = exp², `4` = off, `R` = reset. + +> 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 (Cinematic Bokeh) + +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). + +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 3 m / 8 m / 15 m. + +```sh +cargo run -p wsg-lib --example dof --features "all-prims" +``` + +**Keys**: `1` = cinematic, `2` = subtle, `3` = focus 3 m, `4` = focus 15 m, +`5` = off, `R` = reset. + +> 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. diff --git a/lib/examples/bloom.rs b/lib/examples/effects/bloom.rs similarity index 100% rename from lib/examples/bloom.rs rename to lib/examples/effects/bloom.rs diff --git a/lib/examples/demo.rs b/lib/examples/effects/demo.rs similarity index 100% rename from lib/examples/demo.rs rename to lib/examples/effects/demo.rs diff --git a/lib/examples/dof.rs b/lib/examples/effects/dof.rs similarity index 100% rename from lib/examples/dof.rs rename to lib/examples/effects/dof.rs diff --git a/lib/examples/fog.rs b/lib/examples/effects/fog.rs similarity index 100% rename from lib/examples/fog.rs rename to lib/examples/effects/fog.rs diff --git a/lib/examples/hdr.rs b/lib/examples/effects/hdr.rs similarity index 100% rename from lib/examples/hdr.rs rename to lib/examples/effects/hdr.rs diff --git a/lib/examples/msaa.rs b/lib/examples/effects/msaa.rs similarity index 100% rename from lib/examples/msaa.rs rename to lib/examples/effects/msaa.rs diff --git a/lib/examples/lights/README.md b/lib/examples/lights/README.md new file mode 100644 index 0000000..4a88ff3 --- /dev/null +++ b/lib/examples/lights/README.md @@ -0,0 +1,124 @@ +# Lights, Shadows & Emissive + +Examples covering **lighting**: shadow mapping, isolated light types, and +emissive materials. + +| Example | Run command | What it shows | +|---------|-------------|---------------| +| `shadow` | `cargo run -p wsg-lib --example shadow` | Shadow mapping in isolation (directional light, 4 objects on a floor) | +| `shadow_test` | `cargo run -p wsg-lib --example shadow_test` | Dedicated shadow test: one directional caster, cube on a ground slab, PCF-softened | +| `spot_test` | `cargo run -p wsg-lib --example spot_test` | Isolated spot light: directed beam, penumbra, attenuation | +| `emissive` | `cargo run -p wsg-lib --example emissive` | Emissive materials (increasing intensities 0 → 4.0) | + +> All commands run from the repo root. + +--- + +## `shadow` — Shadow Mapping + +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 +``` + +### Keys + +| 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) | + +### What to observe + +- 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). + +--- + +## `shadow_test` — Dedicated Shadow Mapping Test + +A single **directional** light is configured as the shadow caster +(`Scene::set_shadow_caster(Some(0))`). The cube sits on a large thin ground +slab, so its silhouette is projected as a crisp PCF-softened shadow. With a +small ambient term the shadow is clearly visible and the light/shadow +directions are easy to read: + +1. the **blocker** (cube) casts a directional shadow that stretches along the + ground opposite the light direction — the light sits at the camera's + front-right and low-ish, so the shadow runs clearly across the ground to + the left of the cube, +2. the shadow edge is **softened** by 3×3 PCF (no hard jagged border), +3. the lit faces are bright while the shadowed ground stays near-ambient, + proving the depth comparison is applied per-pixel. + +```sh +cargo run -p wsg-lib --example shadow_test +``` + +--- + +## `spot_test` — Isolated Spot Light + +**Only** a spot light is on (the default directional light is removed via +`clear_lights()`) and the ambient is deliberately **very low**. The rotating +cube therefore appears nearly black except where the spot's cone reaches it — +you clearly see: + +1. a **directed beam** (not an omni halo like the point light), +2. a **smoothed edge** (penumbra) at the cone's limit, +3. the lighting that **follows the cube** as it rotates (the cone is fixed in + world space). + +```sh +cargo run -p wsg-lib --example spot_test +``` + +--- + +## `emissive` — Emissive Materials + +Five spheres in a row with increasing emissive intensities: + +| 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) | + +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 +``` + +### Keys + +| 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× → …) | + +### What to observe + +- 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). diff --git a/lib/examples/emissive.rs b/lib/examples/lights/emissive.rs similarity index 100% rename from lib/examples/emissive.rs rename to lib/examples/lights/emissive.rs diff --git a/lib/examples/shadow.rs b/lib/examples/lights/shadow.rs similarity index 100% rename from lib/examples/shadow.rs rename to lib/examples/lights/shadow.rs diff --git a/lib/examples/shadow_test.rs b/lib/examples/lights/shadow_test.rs similarity index 100% rename from lib/examples/shadow_test.rs rename to lib/examples/lights/shadow_test.rs diff --git a/lib/examples/spot_test.rs b/lib/examples/lights/spot_test.rs similarity index 100% rename from lib/examples/spot_test.rs rename to lib/examples/lights/spot_test.rs diff --git a/lib/examples/meshes/README.md b/lib/examples/meshes/README.md new file mode 100644 index 0000000..8163fd1 --- /dev/null +++ b/lib/examples/meshes/README.md @@ -0,0 +1,118 @@ +# Meshes, Materials & Import + +Examples covering **geometry and materials**: the minimal workflow, the 3D MVP, +PBR shading, file import, and the low-level (non-`App`) workflow. + +| Example | Run command | What it shows | +|---------|-------------|---------------| +| `simple` | `cargo run -p wsg-lib --example simple` | The minimal declarative workflow: a flat two-tone quad, **unlit**, rendered automatically | +| `cube` | `cargo run -p wsg-lib --example cube` | The 3D MVP: a textured (checkerboard) cube, lit (directional + point + spot), spinning | +| `pbr` | `cargo run -p wsg-lib --example pbr` | PBR metallic/roughness + normal mapping | +| `import` | `cargo run -p wsg-lib --example import --features import-obj` | OBJ file import (non-graphical, prints stats to stdout) | +| `manual` | `cargo run -p wsg-lib --example manual` | The **advanced** workflow: `Context`/`Renderer`/`PipelineCache` driven by hand, without the `App` facade | + +> All commands run from the repo root. The `import` example additionally +> requires the `import-obj` Cargo feature. + +--- + +## `simple` — Minimal Declarative Workflow + +The "15 lines, no wgpu" model: `AppBuilder` creates the window + GPU, and +`App::run` drives the update → render → present loop. The scene renders +**automatically** — the default `AppHandler::render` calls +`app.render_scene(frame.view())`. + +The mesh is a flat two-tone quad declared from a `Geometry` (per-vertex +positions + colors) and drawn with the `standard` shader in **unlit** mode +(`renderer.set_unlit(true)`): flat 2D is a special case of 3D, one pipeline for all. + +```sh +cargo run -p wsg-lib --example simple +``` + +No keys — static render. + +--- + +## `cube` — The 3D MVP + +A lit unit cube that rotates, **textured** with a procedural 8×8 checkerboard +via the diffuse path (bind group `@group(2)`). Follows the declarative workflow +(like `simple`): `AppBuilder` + automatic scene, **no wgpu import**. The texture +is generated procedurally (RGBA bytes → `Texture::from_rgba8`) to stay +self-contained; the default camera at (0, 0, 3) frames the cube, and +`update()` rotates the entity via `set_entity_transform` each frame. + +```sh +cargo run -p wsg-lib --example cube +``` + +No keys — the cube spins on its own. + +--- + +## `pbr` — PBR Metallic/Roughness + 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 +``` + +| Key | Action | +|-----|--------| +| Drag (LMB) | Orbit camera | +| Wheel | Zoom | +| `R` | Reset camera | + +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. + +--- + +## `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. + +--- + +## `manual` — Low-level Workflow (no `App` facade) + +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: + +- `Context` (*Manager* layer): GPU lifecycle — `Instance`/`Surface`/`Adapter`/ + `Device`/`Queue`, `configure()` for the swapchain, per-frame surface texture. +- `Renderer` (*Executor* layer): `render(view, mesh, material)` = one object per + submission; `present(frame)`. +- `PipelineCache`: `register_shader(id, path)` then `Material::new(format, id, &mut cache)`. + +The window and GPU are created in winit 0.30's `resumed()` callback +(`run_app` + `ApplicationHandler`). The two-layer architecture is detailed in +[`docs/tech/ARCHI_APP.md`](../../docs/tech/ARCHI_APP.md) and +[`FRAME_LOOP.md`](../../docs/tech/FRAME_LOOP.md). + +```sh +cargo run -p wsg-lib --example manual +``` + +No keys — static render (unlit quad, 4 colors). + +> **Tip**: start with the declarative workflow. The manual workflow doesn't +> render more pixels — it gives more control over command encoding. diff --git a/lib/examples/cube.rs b/lib/examples/meshes/cube.rs similarity index 100% rename from lib/examples/cube.rs rename to lib/examples/meshes/cube.rs diff --git a/lib/examples/import.rs b/lib/examples/meshes/import.rs similarity index 100% rename from lib/examples/import.rs rename to lib/examples/meshes/import.rs diff --git a/lib/examples/manual.rs b/lib/examples/meshes/manual.rs similarity index 100% rename from lib/examples/manual.rs rename to lib/examples/meshes/manual.rs diff --git a/lib/examples/pbr.rs b/lib/examples/meshes/pbr.rs similarity index 100% rename from lib/examples/pbr.rs rename to lib/examples/meshes/pbr.rs diff --git a/lib/examples/simple.rs b/lib/examples/meshes/simple.rs similarity index 100% rename from lib/examples/simple.rs rename to lib/examples/meshes/simple.rs