- Camera enrichie: fov/near/far stockés, Default (pos (0,0,3), 45°, near 0.1, far 100), with_perspective(), projection_matrix(aspect) depuis les params stockés (au lieu de les passer en argument). - Scene porte une caméra active: set_camera()/camera() (défaut Camera::default). - Renderer::render_scene(view, scene, aspect) écrit chaque frame view/proj/ cam_pos réels dans le buffer frame (write_frame_uniforms) avant de dessiner; le Renderer garde le handle du frame_buffer. Le chemin bas-niveau render() conserve les valeurs par défaut (identité). - App::render_scene calcule l'aspect depuis window.inner_size() (le Renderer reste indépendant de la fenêtre). Docs synchronisées: DRAFT (4.3 coche), README (statut 3D-infra + quick ref), PLAN (caméras), ROADMAP (1.1/1.3/1.5/2.3). Validation: check workspace+examples 0 warning, test (Pod + wgsl) OK, doc 0 warning, fmt propre. Le rendu 3D visible attend Étape 5 (brancher standard).
11 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é). (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. (bloqué : dépend des bind groups duPipelineCache, infra de l'Étape 3) - 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)
É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 |