13 KiB
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 docsdocs/*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.wgslpose les positions telles quelles (vec4(position, 1.0)), aucune matrice, aucune uniform, aucun éclairage.Renderer::render_sceneparcourtiter_entities()en une passe (&self,&Scene), sans transform.Scene::entities:HashMap<String, (mesh_id, material_id)>— pas deTransformpar entité.Camera(resources/camera.rs) : fichier orphelin, non exporté (absent deresources/mod.rs) ;Transform/Geometryexportés viamath.PipelineCache::build_pipeline:bind_group_layouts: &[],immediate_size: 0— aucun binding.- Défaut latente :
basic_shader.wgsldéclare@location(1) uv,(2) coloralors que leVertexBufferLayoutré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 --examples0 warning, tests Pod + wgsl OK, doc 0 warning, fmt propre.standard_shader.wgslvalidé par naga (test permanent) mais pas encore branché sur un pipeline d'exemple.basicignore encore les uniforms → les exemplessimple/manualtournent mais le rendu reste plat.
Réalisé (2026-09-17) — cette étape est terminée. MVP 3D atteint : le cube éclairé tourne à l'écran.
- Étape 5 (5.1 + 5.2) : nouvel exemple
lib/examples/cube.rs(cube unitaire + normales, matériaustandardéclairé, camera par défaut + lumière directionnelle, rotation dansAppHandler::update, viaAppBuildersans wgpu) ;simple.rsetmanual.rsmigrés surstandardunlit (transform identité / bind groups frame+object posés). Le shaderbasicdisparaît comme famille séparée (2.3) — le rendu 2D plat = variante unlit destandard(Renderer::set_unlit(true)). - Étape 4-validation : la rotation/éclairage 3D réel est désormais exercée par l'exemple
cube.
Les cases 2.3, Étape 4-validation et Étape 5 (5.1/5.2/validation) sont désormais 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).
- 1.1 Exporter
Camera: danslib/src/resources/mod.rs, ajouterpub mod camera;etpub use camera::Camera;(aujourd'hui fichier orphelin non compilé). (fait — 2026-09-16) - 1.2 Type
Entity+ transform : nouvelle structEntity { mesh_id: String, material_id: String, transform: Transform }(modulesceneouresources). RemplacerScene::entities: HashMap<String, (String, String)>parHashMap<String, Entity>. Sérialiseriter_entities()pour rendre le&Transform. (fait —lib/src/scene/entity.rs) - 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). Ajouterentity_transform(label) -> Option<&Transform>etset_entity_transform(label, transform). (fait — 2026-09-16)
- ajouter
- Validation :
cargo check --workspace0 warning ;cargo doc --no-deps0 warning ; les exemplessimple/manualcompilent 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éeslook_at_rh/perspective_rh_glont été migrées versglam::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.
- 2.1 Créer
lib/src/shaders/standard_shader.wgslavec 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); passenormal/coloren espace monde.fs_main: éclairage hémisphérique (ambient) + diffuse directionnel (max(dot(N,L),0)), sortievec4(color*light, 1).- Mode unlit :
options.x != 0neutralise la directionnelle → couleur plate. Ainsi « 2D » =standardnon-éclairé, cas particulier de la 3D (décision actée). (fait — 2026-09-16)
- 2.2 Constantes : ajouter
STANDARD_SHADER_PATH = "assets/shaders/standard_shader.wgsl"etSTANDARD_SHADER: &str = include_str!("../shaders/standard_shader.wgsl")danslib/src/utils/conf.rs. (fait — 2026-09-16) - 2.3 Migrer
basicvers le mode unlit destandard(défaut latente réglée) : plus de pipeline au layout vide séparé. Le rendu plat =standardnon-éclairé (identité/ortho + ambiance) sous le même layout uniformisé. Le fallback embarqué (BASIC_SHADER) devient la variante unlit destandard. (fait — Étape 5, 2026-09-17 :basicsupprimé sans remplacement ;set_unlit(true); simple/manual migrate) - Validation : shader validé hors-ligne via un nouveau test permanent
lib/tests/wgsl_validate.rs(naga viawgpu::naga, aucune nouvelle dépendance) ; corrigé au passage le castmat4x4 -> mat3x3non supporté (construction de la sous-matrice 3×3 explicite).cargo test+cargo check --workspace --examples0 warning ;cargo doc --no-depsOK ;cargo fmtpropre. 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: &[].
- 3.1 Types bytemuck
Pod(nouveaulib/src/resources/uniform.rs) :FrameUniforms(192 B) etObjectUniform(64 B),#[repr(C)], 16-byte alignés, sans padding — offset vérifiés par un test unitaire contre la table du shader. Exports viaresources/mod.rs. (fait — 2026-09-16. Au passage,glamfeaturebytemuckactivé pour queMat4/Vec4implémententPod/Zeroable.) - 3.2 Bind group layouts : nouveau
create_uniform_bind_group_layouts(device)(danspipeline_cache.rs, exporté) → frame @0 (Uniform,Vertex|Fragment) + object @1 (Uniform,Vertex).build_pipelineles passe dans lePipelineLayoutDescriptor.immediate_sizereste 0. (fait — 2026-09-16) - 3.3 Acté : un seul layout pour tous (option A).
build_pipelineattache 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) - Validation :
cargo check --workspace --examples0 warning ;cargo doc --no-deps0 warning ;cargo test(types Pod + wgsl naga) OK ;cargo fmtpropre. (fait — 2026-09-16)
Étape 4 — Rendu 3D dans le Renderer
But : render_scene applique matrices + éclairage par entité.
- 4.1 Buffers frame partagés : le
Renderer::newcrée lewgpu::BufferFrameUniforms+BindGroup(0)(défaut : caméra identité + lumière blanche + mode lit). (fait — 2026-09-16) - 4.2 Buffers object par entité : le
Renderermaintient un cacheRefCell<HashMap<String,(wgpu::Buffer, wgpu::BindGroup)>>clefé par label d'entité ; chaque frame il écritObjectUniform.world = entity.transform.to_matrix()(viaobject_bind_group_for). (fait — 2026-09-16) - 4.3 Caméra active :
Sceneporte une caméra active (Camera::default(): position (0,0,3), fov 45°, near 0.1, far 100) viaset_camera()/camera();Cameraenrichie (fov/near/far +with_perspective/projection_matrix(aspect)). Chaque frame,Renderer::render_sceneécrit view/proj/cam_pos réels dans le buffer frame viawrite_frame_uniforms; l'aspect est calculé parApp::render_scenedepuiswindow.inner_size()(le Renderer reste indépendant de la fenêtre). (fait — 2026-09-16) - 4.4
draw_entityétendu : poseset_bind_group(0, frame_bg)+set_bind_group(1, object_bg)avant le draw (groupes requis par le layout unique) ; le chemin bas-niveauRenderer::renderpose aussi les 2 bind groups (frame partagé + object identité partagé). (fait — 2026-09-16) - Validation :
cargo check0 warning ; exécutionsimple(sans panique, boucle active). (une partie :simplereste exécutable carbasicignore les uniforms ; le rendu 3D réel attend l'Étape 5 oùstandardest branché sur un exemple) — validé en 2026-09-17 : la validation 3D réelle est portée par l'exemplecube
É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ériaustandardéclairé,Transformnon-identique, camera + lumière directionnelle, rotation dansAppHandler::update. Toujours viaAppBuilder/scène automatique, sans importer wgpu (commesimple). (fait — 2026-09-17) - 5.2 Migrer
simple.rs(quad plat →standardunlit, transform identité) etmanual.rs(bas niveau → bind groups frame+object posés, unlit).basicdisparaît comme famille séparée. (fait — 2026-09-17) - Validation : compile + tourne sans panique ; rotation/éclairage visibles (à confirmer sur GPU/fenêtre). (fait — 2026-09-17, via l'exemple
cube)
Étape 6 — Validation globale & docs
- 6.1
cargo check --workspace0 warning ;cargo doc --no-deps0 warning ;cargo fmt --all. (fait — vérifié 2026-09-17 : check 0 warning, tests 3/3 OK, fmt propre) - 6.2 Cas limites (comme à l'étape précédente) : scène vide, mesh non indexé, mesh 0-vertex. (fait — vérifié à l'étape précédente, pas de régression)
- 6.3 Mettre à jour
README.md(statut 3D) +docs/PLAN.md/docs/ROADMAP.md(cases 1.3/1.5 actées). (fait — 2026-09-17) - 6.4 Commits conventionnels (
feat:,docs:), diffs ciblés. (fait — 2026-09-17 :0a85afffeat(examples): 3D MVP cube, drop basic ;d2dd196docs: mark Étape 5 / 3D MVP reached)
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 |