Files
wsg/docs/DRAFT.md
T

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 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.

É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 : dans lib/src/resources/mod.rs, ajouter pub mod camera; et pub use camera::Camera; (aujourd'hui fichier orphelin non compilé).
  • 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.
  • 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).
  • 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).

É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.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 }
    • @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 : un flag dans FrameUniforms (ou light_color nul) neutralise la directionnelle → couleur plate. Ainsi « 2D » = standard non-éclairé, cas particulier de la 3D (décision actée).
  • 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.
  • 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.
  • Validation : nouveau shaders/mod.rs si include_str le requiert ; cargo check OK (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 (nouveau lib/src/resources/uniform.rs, ou math/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 le mod.rs concerné.
  • 3.2 Bind group layouts : dans build_pipeline, créer 2 BindGroupLayout (frame @0 + object @1, chacun avec un buffer uniform Vertex/Fragment/Vertex|Fragment selon usage) et les passer dans PipelineLayoutDescriptor.bind_group_layouts. immediate_size reste 0 (pas de var<immediate>).
  • 3.3 Acté : un seul layout pour tous (option A). build_pipeline attache toujours les 2 bind groups (frame @0 + object @1). Plus de famille basic au layout vide : tout matériau partage le même layout uniformisé. manual/quad plat migrent (Étape 5).
  • Validation : cargo check 0 warning ; cargo doc 0 warning (types documentés, missing_docs actif).

É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::Buffer FrameUniforms + BindGroup(0) dans Renderer::new (ou à la 1re frame). Écrire chaque frame : view/proj (caméra active) + lumière.
  • 4.2 Buffers object par entité : Renderer maintient un cache RefCell<HashMap<String, (wgpu::Buffer, wgpu::BindGroup)>> clefé par label d'entité (créé à la 1re rencontre), car render_scene(&self, &Scene) est immuable. Chaque frame : écrire ObjectUniform.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 du proj avec l'aspect de la fenêtre (window.inner_size() accessible via App.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-niveau Renderer::render pose aussi les 2 bind groups (frame partagé + object du mesh appelant).
  • Validation : cargo check 0 warning ; exécution simple (sans panique, boucle active) ; manual non-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é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