Files
wsg/docs/ROADMAP.md
T
Jérôme Bousquié ab13fa725e fix(shadow): make Étape 14 shadow visible
The shadow pass was correct but the demo light was much too steep (52°
elevation), so the blocker's shadow fell in a ~0.5-unit sliver tight against
the cube's base and was invisible against the bright ground (offscreen pixel
probe found a single dark pixel). Verified with an offscreen probe using the
real Renderer::render_scene + shadow path:
  - steep front light   (0.6,1.1,0.6)  -> 1 dark pixel   (no visible shadow)
  - shallow side light  (1.0,0.3,0.0)  -> 17 107 pixels  (shadow pipeline OK)
  - tuned front-right   (1.0,0.5,0.0)  -> 16 979 pixels  (clear visible shadow)

The azimuth matters most: from the elevated front-right camera, a shadow cast
toward -z falls behind the cube and is occluded; one cast toward -x runs across
the ground to the left of the cube and reads clearly. Tuned light therefore sits
front-right and low (toward_light (1.0,0.5,0.0)), keeping the front faces lit
while casting a clearly visible PCF-softened shadow.

Also reapply the LessEqual comparison sampler fix (commit 39167ee had set it,
but was later reverted to GreaterEqual by 9a51ff7 while debugging; the probe
confirms LessEqual is the correct, non-inverted test). Correct 'rotating cube'
to 'cube' in README/ROADMAP (shadow_test scene is static).
2026-09-19 21:12:25 +02:00

190 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: Roadmap
title: WSG Engine Development Roadmap
description: Development roadmap for the WSG engine from prototype to full-featured 3D rendering engine
tags: [roadmap, development, planning, wsg-lib, 3d-rendering]
status: stable
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
---
# Roadmap WSG — Prototype → Moteur Complet
> Basé sur l'architecture existante (ARCHI_APP, ARCHI_ARENES, ARCHI_CPU_GPU, ARCHI_RENDU).
> Objectif : prototype fonctionnel d'abord, enrichissement progressif ensuite.
>
> **Point de départ (état réel au 2026-09-16 — la source de vérité est README.md).**
> Les fondations suivantes existent et fonctionnent déjà ; cette roadmap décrit la **trajectoire à
> venir** à partir de cet état (elle reprend les étapes 1-4 du README avant la montée GPU-driven) :
> - Workflow manuel (`Context` + `Renderer` + `PipelineCache`) : ✅ fonctionnel (exemple `manual`).
> - Façade `App` / `AppBuilder` / `AppHandler` : ✅ **Scene auto-render** (2026-09-16) — la vue de frame
> est exposée (`Frame::view()`), `render()` dessine la scène en une passe groupée
> (`App::render_scene`) et la présentation est automatique dans `App::run` (exemple `simple`).
> - `Scene` avec identifiants **String** (décision prise — voir tableau Notes de Décision) : 🚧 enregistrement seul.
> - `Camera` / `Transform` et `glam` : types et mathématiques présents (`math/`, `resources/camera.rs`),
> initialement non branchés au pipeline — **désormais branchés** (caméra active + matrices monde écrites
> chaque frame, Étape 4.3, 2026-09-16 ; voir §1.1/1.5 ci-dessous).
> **Étape suivante (résolue 2026-09-17).** « 3D + éclairage Phong » (ROADMAP 1.3 + 1.5) est **atteinte** :
> le rendu automatique n'est plus plat. L'infrastructure (Étapes 3+4, 2026-09-16) — `standard_shader.wgsl`
> Phong (matrice `projection * view * world` + lumière directionnelle), uniform buffers branchés
> (frame : view/proj/cam_pos + lumière ; par mesh : `world` dérivé du `Transform`), `Renderer` écrivant
> chaque frame la caméra active et la matrice monde de chaque entité — est **branchée** sur l'exemple
> `cube` (Étape 5, 2026-09-17) : un cube unitaire éclairé qui tourne à l'écran via `App::render_scene`.
> Le shader `basic` est supprimé : le 2D plat devient la variante **unlit** de `standard`
> (`Renderer::set_unlit(true)`). Objectif MVP **atteint**.
---
## Phase 1️⃣ — Prototype MVP : Un Mesh 3D éclairé à l'écran
**Objectif** : Afficher un cube (ou autre mesh) 3D avec un éclairage Phong basique.
### 1.1 Dépendances & Mathématiques
- [x] `glam = "0.33"` ajouté (`lib/Cargo.toml`) — déjà présent, utilisé par `math/transform.rs` et `resources/camera.rs`
- [x] `slotmap` **retiré** — décision prise : **String IDs pour le MVP** ; slotmap reporté à l'étape "handles typés" (voir Notes de Décision)
- [x] Module `math/` / `transform.rs`:
- [x] Struct `Transform { translation: Vec3, rotation: Quat, scale: Vec3 }`
- [x] Méthode `to_matrix() -> Mat4` pour calculer la matrice locale
- [x] Struct `Camera { position: Vec3, target: Vec3, up: Vec3 }` : resources/camera.rs — enrichi en Étape 4.3 (fov/near/far + `with_perspective`)
- [x] Fonctions `view_matrix()` et `projection_matrix(fov, aspect, near, far)` (Étape 4.3 : `projection_matrix(aspect)` utilise fov/near/far stockés)
### 1.2 Geometry & Mesh
- [x] Créer struct `Geometry` (math/geometry.rs) — **fait** :
- [x] `positions: Vec<[f32; 3]>` (obligatoire)
- [x] `indices: Option<Vec<u16>>` (optionnel)
- [x] `normals: Option<Vec<[f32; 3]>>` (pour Phong) — plus `uvs: Option<Vec<[f32; 2]>>`
- [x] `colors: Option<Vec<[f32; 4]>>` — **fait (Étape 8, 8.1, 2026-09-18)** : décidé en DRAFT Étape 8 (D1) ;
le shader lit la couleur unlit, elle est donc portée dans `Geometry`. Conversion
`Geometry -> Vec<Vertex>` via `Geometry::to_vertices()` (D6) pour l'upload.
- [x] Refactorer `Mesh` pour contenir — **fait (Étape 8, 8.3, 2026-09-18)** :
- [x] `geometry: Arc<Geometry>` (rétention CPU, D5) + accesseur `geometry()`
- [x] `vertex_buffer: wgpu::Buffer`
- [x] `index_buffer: Option<wgpu::Buffer>`
- Construction via `Mesh::from_geometry(device, Arc<Geometry>, material)` (D4) ; les anciennes
voies `Mesh::new`/`with_material` (`&[Vertex]`) sont supprimées.
- `Scene::create_mesh(id, Geometry, Option<&str>)` (8.4) ; exemples cube/simple/manual réécrits
sur `Geometry` (8.5).
- [x] Ajouter un mesh de test (cube unitaire) en exemple — **fait** (helper `cube_geometry` dans l'exemple `cube`, Étape 5, 2026-09-17)
> **État (2026-09-18)** : `Mesh` porte son matériau (`mesh.material: Option<Arc<Material>>`, Étape 7) et
> une source de vérité CPU partagée (`geometry: Arc<Geometry>`, Étape 8). `Mesh` ne porte **pas** de
> `transform` : un même mesh est partagé par plusieurs entités ; le `transform` vit sur `Entity`.
### 1.3 Shader Phong Minimal
- [x] Créer `standard_shader.wgsl` (Étape 2, 2026-09-16) :
- [x] Vertex shader : projection * view * world * position
- [x] Fragment shader : éclairage directionnel (+ hémisphérique)
- [x] Uniforms : `view`, `proj`, `cam_pos`, `light_dir`, `light_color`, `options`
- [x] Mettre à jour `Material` / pipeline pour supporter les uniforms du shader Phong (bind group layouts frame+object, Étape 3) — **désormais branché** sur l'exemple `cube` (Étape 5, 2026-09-17)
### 1.4 Scene avec identifiants (MVP : String IDs)
- [x] `Scene` implémentée avec **String IDs** (`HashMap<String, Arc<Mesh>>`, `...Material`, entités) — état actuel validé ; décision : rester en String IDs pour le MVP
- [x] Méthodes : `add_mesh()`, `get_mesh()`, `add_material()`, `add_entity()`, `iter_entities()`, `remove_entity()`
- [x] Caméra active dans la `Scene` : `set_camera()` / `camera()` (Étape 4.3)
- [ ] **Reporté (étape "Handles typés")** : migrer vers `slotmap` générationnel (`MeshId`/`MaterialId`) quand l'éviction/les performances le justifieront
### 1.5 Rendu du Prototype
- [x] Uniform buffer pour la frame : `view`, `proj`, `cam_pos`, `light_dir` (Étapes 3+4) — écrit chaque frame depuis la caméra active
- [x] Uniform buffer par mesh : `world` (calculée sur CPU depuis `transform.to_matrix()`, Étape 4.2)
- [x] `Renderer::render_scene()` itère sur les entités de la Scene et dessine chacune (liaison bind groups frame+object)
- [x] Exemple fonctionnel : un cube éclairé tourne à l'écran — **fait** (Étape 5, 2026-09-17 : brancher `standard` sur l'exemple `cube` + mesh cube + rotation via `App::render_scene`)
---
## Phase 2️⃣ — Scène enrichie
**Objectif** : Étoffer la `Scene` au-delà du MVP.
> Les ressources sont, pour le MVP, identifiées par **String IDs** (décision actée — voir Notes de
> Décision). Une migration vers des **handles typés** (slotmap générationnel) reste planifiée quand
> l'éviction/les performances le justifieront (voir 1.4, « reporté »).
### 2.1 Camera dans la Scene
- [x] Intégrer `Camera` comme ressource de la Scene (Étape 4.3 : `Scene::set_camera` / `camera()`, caméra active unique)
- [ ] Permettre plusieurs caméras (actuelle/inactive) et une sélection par identifiant (`scene.set_active_camera(camera_id)`)
- [ ] Exposer une caméra orbitale contrôlable (exemple final, Phase 5)
---
## Phase 3️⃣ — GPU-Driven Rendering
**Objectif** : Déléguer les calculs de transformation et culling au GPU (suivre ARCHI_CPU_GPU.md).
### 3.1 Compute Shader
- [ ] Buffer `TransformBuffer` (CPU → GPU) : positions/rotations/échelles brutes
- [ ] Buffer `MatrixBuffer` (GPU calculé) : World Matrices finales
- [ ] Compute shader : calcul des World Matrices pour tous les meshes
### 3.2 Frustum Culling GPU
- [ ] Ajouter `BBox` dans `Geometry` (center + extents)
- [ ] Buffer `BoundingBoxBuffer` (CPU → GPU, statique)
- [ ] Compute shader : culling basé sur la frustum de caméra
- [ ] Buffer `IndirectDrawBuffer` rempli par le GPU
### 3.3 Rendu Indirect
- [ ] `draw_indexed_indirect()` au lieu de draw calls individuels
- [ ] Un seul command draw pour tous les objets visibles
---
## Phase 4️⃣ — Fonctionnalités Avancées
**Objectif** : Qualité visuelle et performances.
### 4.1 Textures
- [x] Struct `Texture` avec chargement d'image *(Étape 10 : resources::Texture, from_rgba8/bytes/file, Rgba8UnormSrgb, sampler linear/repeat)*
- [x] Ajouter `uvs: Option<Vec<[f32; 2]>>` dans `Geometry` *(prérequis Étape 10, déjà présent dans le code — seul l'échantillonnage manquait)*
- [x] BindGroup pour les textures dans le shader *(Étape 10 : groupe @2 sampler+texture sur toutes les pipelines, placeholder blanc)*
- [x] `Material` supporte une texture diffuse *(Étape 10 : Material.texture + texture_bind_group, placeholder si None)*
### 4.2 Éclairage avancé
- [x] Lumières hémisphériques *(déjà dans le `standard_shader` : mélange hémisphérique, Étape 2)*
- [x] Support multi-lumières (directionnelles, ponctuelles) *(Étape 12, 2026-09-18 : liste globale dans la `Scene`, tableau `FrameUniforms.lights[8]`, shader accumule ambiant + directionnelles + ponctuelles, `MAX_LIGHTS = 8`)*
- [x] Lumières spot (cône + angle) *(Étape 13, 2026-09-18 : même struct `Light` + champ `dir_angle` (axe du cône + cos du demi-angle) + compteur `num_spot` ; boucle d'accumulation dédiée dans le shader avec pénombre lissée et atténuation linéaire ; `Scene::add_spot_light`)*
- [x] Shadows (optionnel) *(Étape 14, 2026-09-19 : shadow mapping mono-lumière — light unique
(directionnelle **ou** spot) choisie par `Scene::set_shadow_caster(index)` ; depth-only `shadow_shader.wgsl`
+ pipeline ombre dans le `Renderer` (shadow map 1024² Depth32Float, bias slope-scaled) ; pass
`render_shadow_map` en tête de `render_scene` ; PCF 3×3 + comparateur dans `standard_shader.wgsl`
(groupe @3 partagé, lié mais non échantillonné quand désactivé → non-régression). Ombres **éteintes
par défaut**. Exemple `shadow_test` : cube projetant une ombre douce sur un sol.)*
### 4.3 Optimisations
- [ ] Batching par Material (réduction des state changes GPU)
- [ ] Level of Detail (LOD)
- [ ] HDR + Tone Mapping (optionnel)
### 4.4 Gestion du Resize (cycle de vie Surface + Depth)
- [x] Handler `WindowEvent::Resized` dans `AppRunner::window_event` (`app.rs`) *(Étape 11, 2026-09-18)*
→ recalculer `size`, prévenir de ne pas rendre tant que la taille est invalide (0).
- [x] Reconfigurer la surface (`Context::configure`) à la nouvelle taille.
- [x] Recréer la depth texture à la nouvelle taille (`Renderer::resize_depth(width, height)`)
— le helper `create_depth_texture` isolé (Étape 9, D3) rend ce recreate trivial.
- [x] Collecte du nouveau format si la configuration change (srgb etc.) → re-valider la compat pipeline.
*(D4 : `App::resize` compare l'ancien/nouveau format et re-synchronise Renderer (`set_format`) + Scene (`init_gpu`) ; cas pathologique, structuré non exercé couramment)*
> Géré en Étape 11 (2026-09-18) : la surface est désormais reconfigurée à chaque `Resized` et la
> depth texture recréée en même temps (helper `create_depth_texture` isolé, Étape 9, D3). Le present
> mode FIFO reste figé (voir Notes de Décision).
---
## Phase 5️⃣ — Documentation & Polish
- [ ] Exemple complet : mesh texturé, éclairé, avec caméra orbitale
- [ ] Documentation API (`docs/ARCHI_SCENE.md`)
- [ ] Tests unitaires : `Geometry`, `Scene`, `Transform`
- [ ] README mis à jour avec les nouvelles fonctionnalités
---
## Notes de Décision
| Décision | Raison |
|----------|--------|
| **Normals dès Phase 1** | Nécessaires pour le shader Phong ; sans elles, pas d'éclairage |
| **BBox en Phase 3** | Utile uniquement pour le frustum culling GPU |
| **World Matrix CPU → MVP, GPU → Phase 3** | Le MVP est plus simple avec un uniform par mesh ; la migration GPU-driven est progressive |
| **String IDs pour le MVP, slotmap reporté** | Le code et le README utilisent des String IDs (simples, sûrs, figés avant la boucle de rendu) ; `ARCHI_ARENES.md` reste la cible "handles typés" pour plus tard. La dépendance `slotmap` a été retirée tant qu'elle est inutilisée |
| **Present mode FIFO figé pour l'instant** | Le swapchain utilise `PresentMode::Fifo` avec `desired_maximum_frame_latency: 2` (double buffering vsync) — défaut sûr : pas de tearing, énergie minimale, zéro artefact. On **gèle ce choix** ; `Mailbox` (triple buffering) pourra être exposé en option et `Immediate` restera réservé à l'offscreen, **on s'occupera du present mode le moment venu** (quand le pipeline GPU-driven arrivera, Phase 3) — ce n'est pas bloquant pour les étapes 1-2 |
| **Resize géré (avec recréation de la depth texture), acté en D3 (2026-09-18), réalisé en Étape 11 (2026-09-18)** | L'app reconfigure désormais la surface et recrée la depth texture **en même temps** à chaque `Resized` (`App::resize` → `Context::configure` + `Renderer::resize_depth`), via le helper `create_depth_texture` isolé. Vérifié au runtime (exemple `cube`) : pas de crash, pas d'artefact, aspect correct |