e8ff364d0d
- Light étendu à 4×Vec4 (64 o) : dir_angle (axe du cône + cos demi-angle) - FrameUniforms 576→704 o : compteur num_spot, _pad[1] - Lights + spot: Vec<Light> ; into_frame_array renvoie (arr, n_dir, n_point, n_spot) - Scene::add_spot_light(pos, dir, color, intensity, radius, half_angle) ; clear_lights inclut spot - standard_shader.wgsl : 3e boucle d'accumulation (pénombre lissée ±0.1 rad + atténuation linéaire) - exemple cube : lumière spot verte pointée vers le cube - Docs : shaders/resources README (704 B), README roadmap (item 11), ROADMAP (item coché) - Build/test/fmt OK (5 tests + validation WGSL + doctests) ; non-régression par défaut
185 lines
12 KiB
Markdown
185 lines
12 KiB
Markdown
---
|
|
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`)*
|
|
- [ ] Shadows (optionnel)
|
|
|
|
### 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 |
|