Files
wsg/docs/DRAFT.md
T
Jérôme Bousquié 43e8bfb40a Update DRAFT.md
2026-09-16 19:19:56 +02:00

160 lines
12 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.
# DRAFT — Plan d'implémentation : « 3D + éclairage Phong »
> **Usage.** Ce fichier (dans `docs/`) sert de brouillon pour le plan détaillé de l'étape en cours.
> **Son contenu est effacé au début de chaque nouvelle étape.** La source de vérité de l'état est
> le code + README.md ; les autres docs `docs/*` restent stables.
>
> **Étape.** 3D + éclairage Phong (ROADMAP 1.3 + 1.5). Objectif MVP : **un mesh 3D éclairé à l'écran**,
> rendu automatiquement par la boucle `App` (Scene auto-render déjà en place).
>
> **État de départ vérifié.**
> - Rendu automatique fonctionnel mais **plat** : `basic_shader.wgsl` pose les positions telles quelles
> (`vec4(position, 1.0)`), aucune matrice, aucune uniform, aucun éclairage.
> - `Renderer::render_scene` parcourt `iter_entities()` en une passe (`&self`, `&Scene`), sans transform.
> - `Scene::entities` : `HashMap<String, (mesh_id, material_id)>` — pas de `Transform` par entité.
> - `Camera` (`resources/camera.rs`) : **fichier orphelin, non exporté** (absent de `resources/mod.rs`) ;
> `Transform`/`Geometry` exportés via `math`.
> - `PipelineCache::build_pipeline` : `bind_group_layouts: &[]`, `immediate_size: 0` — aucun binding.
> - Défaut latente : `basic_shader.wgsl` déclare `@location(1) uv`, `(2) color` alors que le
> `VertexBufferLayout` réel expose `(1) normal`, `(2) uv`, `(3) color`.
---
## Point d'étape — 2026-09-16 (fin de session, reprise sur autre machine)
**État : infra posée, MVP 3D pas encore atteint.** Dernier commit : `f10e249`
(`feat(renderer): active camera wired to frame uniforms (Étape 4.3)`), dépôt propre.
**Fait et committé (bases pour la reprise)**
- Étapes **1, 2.1, 2.2, 3, 4** du DRAFT → ✅ (détails cochés ci-dessous).
- `cargo check --workspace --examples` 0 warning, tests Pod + wgsl OK, doc 0 warning, fmt propre.
- `standard_shader.wgsl` validé par naga (test permanent) mais **pas encore branché** sur un pipeline d'exemple.
- `basic` ignore encore les uniforms → les exemples `simple`/`manual` tournent mais le rendu reste plat.
**Prochaine session — reprendre à (dans l'ordre)**
1. **Étape 5** : créer `lib/examples/cube.rs` (cube unitaire + `standard` éclairé + rotation, via `AppBuilder` sans wgpu), puis migrer `simple`/`manual` (2.3 + 5.2 : `basic` → variante unlit de `standard`).
2. **Validation réelle** Étape 4/5 : exécuter les exemples, confirmer rotation/éclairage sur GPU/fenêtre.
3. **Étape 6** : cas limites + update `README.md`/`PLAN.md`/`ROADMAP.md` (cases 1.3/1.5/2.3) + commits.
> Les cases 2.3, Étape 4-validation, Étape 5 et Étape 6 restent **non cochées** ci-dessous = état exact.
---
## Étape 1 — Fondations data : Transform + Camera exposées
**But** : donner à chaque entité un `Transform` et rendre `Camera` utilisable via l'API publique, **sans**
toucher au rendu (pure façade de données, validable par compilation).
- [X] 1.1 **Exporter `Camera`** : dans `lib/src/resources/mod.rs`, ajouter
`pub mod camera;` et `pub use camera::Camera;` (aujourd'hui fichier orphelin non compilé). *(fait — 2026-09-16)*
- [X] 1.2 **Type `Entity` + transform** : nouvelle struct
`Entity { mesh_id: String, material_id: String, transform: Transform }` (module `scene` ou `resources`).
Remplacer `Scene::entities: HashMap<String, (String, String)>` par
`HashMap<String, Entity>`. Sérialiser `iter_entities()` pour rendre le `&Transform`.
*(fait — `lib/src/scene/entity.rs`)*
- [X] 1.3 **Compat API** : garder `add_entity(label, mesh_id, material_id)` (transform identité par défaut)
+ ajouter `add_entity_with_transform(label, mesh_id, material_id, transform)`.
Ajouter `entity_transform(label) -> Option<&Transform>` et `set_entity_transform(label, transform)`.
*(fait — 2026-09-16)*
- [X] **Validation** : `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; les exemples
`simple`/`manual` compilent inchangés (défaut : identité ⇒ même rendu). *(fait — 0 warning. Au passage,
`camera.rs` étant désormais compilée, les fonctions glam dépréciées `look_at_rh`/`perspective_rh_gl` ont été
migrées vers `glam::camera::rh::view::look_at_mat4` / `glam::camera::rh::proj::opengl::perspective`.)*
## Étape 2 — Shader Phong `standard_shader.wgsl`
**But** : produire un rendu 3D éclairé via un nouveau shader, sans encore le brancher.
- [X] 2.1 **Créer `lib/src/shaders/standard_shader.wgsl`** avec le **contrat vertex correct** :
`@location(0) position : vec3`, `(1) normal : vec3`, `(2) uv : vec2`, `(3) color : vec4`.
- `@group(0) @binding(0)` : `FrameUniforms { view: mat4, proj: mat4, cam_pos: vec4, light_dir: vec4, light_color: vec4, options: vec4<u32> }` (options.x = unlit flag)
- `@group(1) @binding(0)` : `ObjectUniform { model: mat4 }`
- `vs_main` : `clip_position = proj * view * model * vec4(position,1)` ; passe `normal`/`color` en espace monde.
- `fs_main` : éclairage hémisphérique (ambient) + diffuse directionnel (max(dot(N,L),0)), sortie `vec4(color*light, 1)`.
- **Mode unlit** : `options.x != 0` neutralise la directionnelle → couleur plate. Ainsi « 2D » = `standard`
non-éclairé, **cas particulier de la 3D** (décision actée). *(fait — 2026-09-16)*
- [X] 2.2 **Constantes** : ajouter `STANDARD_SHADER_PATH = "assets/shaders/standard_shader.wgsl"` et
`STANDARD_SHADER: &str = include_str!("../shaders/standard_shader.wgsl")` dans `lib/src/utils/conf.rs`.
*(fait — 2026-09-16)*
- [ ] 2.3 **Migrer `basic` vers le mode unlit de `standard`** (défaut latente réglée) : plus de pipeline au
**layout vide séparé**. Le rendu plat = `standard` non-éclairé (identité/ortho + ambiance) sous le **même
layout uniformisé**. Le fallback embarqué (`BASIC_SHADER`) devient la variante unlit de `standard`.
*(bloqué : dépend des bind groups du `PipelineCache`, infra de l'Étape 3)*
- [X] **Validation** : shader validé hors-ligne via un **nouveau test permanent** `lib/tests/wgsl_validate.rs`
(naga via `wgpu::naga`, aucune nouvelle dépendance) ; corrigé au passage le cast `mat4x4 -> mat3x3` non
supporté (construction de la sous-matrice 3×3 explicite). `cargo test` + `cargo check --workspace --examples`
0 warning ; `cargo doc --no-deps` OK ; `cargo fmt` propre. Le shader n'est pas encore compilé par un pipeline
(Étape 3). *(fait — 2026-09-16)*
## Étape 3 — Infrastructure uniforms dans le `PipelineCache`
**But** : permettre aux pipelines de recevoir des uniforms (bind groups) au lieu de `bind_group_layouts: &[]`.
- [X] 3.1 **Types bytemuck `Pod`** (nouveau `lib/src/resources/uniform.rs`) : `FrameUniforms` (192 B) et
`ObjectUniform` (64 B), `#[repr(C)]`, 16-byte alignés, sans padding — offset vérifiés par un test
unitaire contre la table du shader. Exports via `resources/mod.rs`. *(fait — 2026-09-16. Au passage,
`glam` feature `bytemuck` activé pour que `Mat4`/`Vec4` implémentent `Pod`/`Zeroable`.)*
- [X] 3.2 **Bind group layouts** : nouveau `create_uniform_bind_group_layouts(device)` (dans
`pipeline_cache.rs`, exporté) → frame @0 (`Uniform`, `Vertex|Fragment`) + object @1 (`Uniform`, `Vertex`).
`build_pipeline` les passe dans le `PipelineLayoutDescriptor`. `immediate_size` reste 0.
*(fait — 2026-09-16)*
- [X] 3.3 **Acté : un seul layout pour tous** (option A). `build_pipeline` attache **toujours** les 2 bind
groups (frame @0 + object @1), même si le shader ne les lit pas (validation wgpu : layout╱bind group).
*(fait — 2026-09-16)*
- [X] **Validation** : `cargo check --workspace --examples` 0 warning ; `cargo doc --no-deps` 0 warning ;
`cargo test` (types Pod + wgsl naga) OK ; `cargo fmt` propre. *(fait — 2026-09-16)*
## Étape 4 — Rendu 3D dans le `Renderer`
**But** : `render_scene` applique matrices + éclairage par entité.
- [X] 4.1 **Buffers frame partagés** : le `Renderer::new` crée le `wgpu::Buffer` `FrameUniforms` + `BindGroup(0)`
(défaut : caméra identité + lumière blanche + mode lit). *(fait — 2026-09-16)*
- [X] 4.2 **Buffers object par entité** : le `Renderer` maintient un cache
`RefCell<HashMap<String,(wgpu::Buffer, wgpu::BindGroup)>>` clefé par label d'entité ; chaque frame il
écrit `ObjectUniform.world = entity.transform.to_matrix()` (via `object_bind_group_for`). *(fait — 2026-09-16)*
- [X] 4.3 **Caméra active** : `Scene` porte une caméra active (`Camera::default()` : position (0,0,3),
fov 45°, near 0.1, far 100) via `set_camera()` / `camera()` ; `Camera` enrichie (fov/near/far +
`with_perspective` / `projection_matrix(aspect)`). Chaque frame, `Renderer::render_scene` écrit
view/proj/cam_pos réels dans le buffer frame via `write_frame_uniforms` ; l'aspect est calculé par
`App::render_scene` depuis `window.inner_size()` (le Renderer reste indépendant de la fenêtre).
*(fait — 2026-09-16)*
- [X] 4.4 **`draw_entity` étendu** : pose `set_bind_group(0, frame_bg)` + `set_bind_group(1, object_bg)` avant le
draw (groupes requis par le layout unique) ; le chemin bas-niveau `Renderer::render` pose aussi les 2 bind
groups (frame partagé + object identité partagé). *(fait — 2026-09-16)*
- [ ] **Validation** : `cargo check` 0 warning ; exécution `simple` (sans panique, boucle active). *(une partie :
`simple` reste exécutable car `basic` ignore les uniforms ; le rendu 3D réel attend l'Étape 5 où `standard` est
branché sur un exemple)*
## Étape 5 — Exemple 3D (cube éclairé)
**But** : démontrer l'objectif MVP à l'écran et **migrer** les exemples sur le pipeline unifié.
- [ ] 5.1 **Nouvel exemple `lib/examples/cube.rs`** : cube unitaire (positions + normales), matériau
`standard` éclairé, `Transform` non-identique, camera + lumière directionnelle, rotation dans `AppHandler::update`.
Toujours via `AppBuilder`/scène automatique, **sans importer wgpu** (comme `simple`).
- [ ] 5.2 **Migrer `simple.rs`** (quad plat → `standard` **unlit**, transform identité) et **`manual.rs`** (bas niveau
→ bind groups frame+object posés, unlit). `basic` disparaît comme famille séparée.
- [ ] **Validation** : compile + tourne sans panique ; rotation/éclairage visibles (à confirmer sur GPU/fenêtre).
## Étape 6 — Validation globale & docs
- [ ] 6.1 `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; `cargo fmt --all`.
- [ ] 6.2 Cas limites (comme à l'étape précédente) : scène vide, mesh non indexé, mesh 0-vertex.
- [ ] 6.3 Mettre à jour `README.md` (statut 3D) + `docs/PLAN.md`/`docs/ROADMAP.md` (cases 1.3/1.5 actées).
- [ ] 6.4 Commits conventionnels (`feat:`, `docs:`), diffs ciblés.
---
## Décisions actées (verrouillées avant l'implémentation)
| Décision | Option proposée | Justification |
|----------|-----------------|---------------|
| Schéma uniforms | **Acté : 2 bind groups** — frame partagé (@0) + object par entité (@1) | Étendu, portable sur tous backends (Metal/DX12/Vulkan) ; `var<immediate>` neuf, limites de taille et hazard d'écriture par objet ; 2 binds/draw seulement, trivialement « pipeline bind-less » plus tard |
| Emplacement types uniforms | **Acté : `resources/uniform.rs`** (`FrameUniforms`, `ObjectUniform`, types `Pod` bytemuck) | Couche de données GPU (avec Camera/Mesh/Material/Vertex) ; préserve `math/` pur (sans bytemuck ni couplage wgpu) |
| Cache object buffer | `RefCell<HashMap<label, (Buffer, BindGroup)>>` dans `Renderer` | `render_scene(&self)` immuable ; MVP petit nombre d'entités |
| Transform dans l'entité | `Entity { mesh_id, material_id, transform }` + `add_entity_with_transform` | `add_entity` garde sa signature (transform identité) |
| Layout pipeline | **Acté : un seul layout pour tous** (frame @0 + object @1) ; `basic` unlit = variante de `standard` | 2D = cas particulier 3D (décision utilisateur) ; supprime la fourchette à deux layouts pour toujours |
| Exemple démo | Nouvel exemple `cube.rs` (éclairé) ; `simple.rs` et `manual.rs` **migrés** vers le pipeline unifié (unlit) | Démontre le 3D sans dédoubler ; cohérent avec « un seul layout pour tous » |
| Correction `basic_shader.wgsl` | **Supprimer** `basic` comme pipeline séparé ; le quad plat devient `standard` unlit | 2D ⊂ 3D : pas de famille de pipeline dédiée |