9.5 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.
É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é). - 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. - 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).
- ajouter
- Validation :
cargo check --workspace0 warning ;cargo doc --no-deps0 warning ; les exemplessimple/manualcompilent inchangés (défaut : identité ⇒ même rendu).
É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 }@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 : un flag dans
FrameUniforms(oulight_colornul) neutralise la directionnelle → couleur plate. Ainsi « 2D » =standardnon-éclairé, cas particulier de la 3D (décision actée).
- 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. - 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. - Validation : nouveau
shaders/mod.rssi include_str le requiert ;cargo checkOK (le shader n'est pas encore compilé par un pipeline tant que l'Étape 3 ne le charge pas).
É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, oumath/uniform.rs) :#[repr(C)] #[derive(Pod, Zeroable, Copy, Clone)] FrameUniforms(voir 2.1)#[repr(C)] #[derive(...)] ObjectUniform { model: Mat4 }- (alignement 16 octets : utiliser
Vec4/tableaux pour éviter le padding). Exporter via lemod.rsconcerné.
- 3.2 Bind group layouts : dans
build_pipeline, créer 2BindGroupLayout(frame @0 + object @1, chacun avec un buffer uniformVertex/Fragment/Vertex|Fragmentselon usage) et les passer dansPipelineLayoutDescriptor.bind_group_layouts.immediate_sizereste 0 (pas devar<immediate>). - 3.3 Acté : un seul layout pour tous (option A).
build_pipelineattache toujours les 2 bind groups (frame @0 + object @1). Plus de famillebasicau layout vide : tout matériau partage le même layout uniformisé.manual/quad plat migrent (Étape 5). - Validation :
cargo check0 warning ;cargo doc0 warning (types documentés,missing_docsactif).
Étape 4 — Rendu 3D dans le Renderer
But : render_scene applique matrices + éclairage par entité.
- 4.1 Buffers frame partagés : créer le
wgpu::BufferFrameUniforms+BindGroup(0)dansRenderer::new(ou à la 1re frame). Écrire chaque frame : view/proj (caméra active) + lumière. - 4.2 Buffers object par entité :
Renderermaintient un cacheRefCell<HashMap<String, (wgpu::Buffer, wgpu::BindGroup)>>clefé par label d'entité (créé à la 1re rencontre), carrender_scene(&self, &Scene)est immuable. Chaque frame : écrireObjectUniform.world = entity.transform.to_matrix()+set_bind_group(1, ...). - 4.3 Caméra active : ajouter
scene.set_active_camera(Camera)/scene.active_camera() -> Option<&Camera>. Calcul duprojavec l'aspect de la fenêtre (window.inner_size()accessible viaApp.window). - 4.4
draw_entityétendu :set_bind_group(0, frame_bg)+set_bind_group(1, object_bg)avant le draw, pour tout matériau (layout unique). Le chemin bas-niveauRenderer::renderpose aussi les 2 bind groups (frame partagé + object du mesh appelant). - Validation :
cargo check0 warning ; exécutionsimple(sans panique, boucle active) ;manualnon-régressif (chemin bas-niveau).
É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). - 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. - Validation : compile + tourne sans panique ; rotation/éclairage visibles (à confirmer sur GPU/fenêtre).
Étape 6 — Validation globale & docs
- 6.1
cargo check --workspace0 warning ;cargo doc --no-deps0 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 |