feat(renderer): active camera wired to frame uniforms (Étape 4.3)

- 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).
This commit is contained in:
Jérôme Bousquié
2026-09-16 17:25:50 +02:00
parent c1e07b42b4
commit f10e249898
8 changed files with 159 additions and 52 deletions
+35 -3
View File
@@ -23,8 +23,9 @@ use crate::core::Frame;
use crate::math::Transform;
use crate::pipeline::create_uniform_bind_group_layouts;
use crate::resources::uniform::{FRAME_UNIFORMS_SIZE, OBJECT_UNIFORM_SIZE};
use crate::resources::{FrameUniforms, Material, Mesh, ObjectUniform};
use crate::resources::{Camera, FrameUniforms, Material, Mesh, ObjectUniform};
use crate::scene::Scene;
use glam::Vec4;
use std::cell::RefCell;
use std::collections::HashMap;
@@ -43,6 +44,9 @@ pub struct Renderer {
format: wgpu::TextureFormat,
/// Bind group layout for the per-object uniforms (group 1) — must match every pipeline layout.
object_layout: wgpu::BindGroupLayout,
/// Shared per-frame uniform buffer handle — kept so the camera matrices can be rewritten each
/// frame (`render_scene`) and shipped to the GPU before the frame bind group is used.
frame_buffer: wgpu::Buffer,
/// Shared per-frame uniform buffer + bind group (camera + lights). Written each frame (`render_scene`).
frame_bind_group: wgpu::BindGroup,
/// Shared per-object bind group (identity model) used by the low-level `render` path.
@@ -110,12 +114,33 @@ impl Renderer {
device,
format,
object_layout,
frame_buffer,
frame_bind_group,
shared_object_bind_group,
object_cache: RefCell::new(HashMap::new()),
}
}
/// Rewrites the shared per-frame uniform buffer from the scene's active camera and the current
/// viewport aspect, then returns the frame bind group wired to that buffer. Called at the start of
/// every `render_scene` so the GPU sees the latest camera matrices and camera position (Étape 4.3).
///
/// The directional light stays at the `FrameUniforms::default()` values (white, along +Z) — scene
/// lighting configuration is a later step; only the camera-driven fields are derived from `camera`.
/// Inputs: camera (the scene's active camera), aspect (viewport width / height).
fn write_frame_uniforms(&self, camera: &Camera, aspect: f32) {
let frame = FrameUniforms {
view: camera.view_matrix(),
proj: camera.projection_matrix(aspect),
cam_pos: camera.position.extend(1.0),
light_dir: Vec4::new(0.0, 0.0, 1.0, 0.0),
light_color: Vec4::ONE,
options: [0, 0, 0, 0],
};
self.queue
.write_buffer(&self.frame_buffer, 0, bytemuck::bytes_of(&frame));
}
/// Orchestrates rendering of a single object: binds Material pipeline + Mesh vertex data into a RenderPass,
/// then submits commands to the GPU queue for execution. Called per-frame by the orchestrator (main.rs).
/// Inputs: view (TextureView color attachment target), mesh (geometry to render), material (shader+pipeline).
@@ -163,8 +188,15 @@ impl Renderer {
/// This avoids allocating a separate encoder and render pass per entity (which the low-level
/// `render` does), minimizing GPU submissions. Called automatically each frame by the default
/// `AppHandler::render` through `App::render_scene`.
/// Inputs: view — the frame's texture view color attachment; scene — the scene whose entities are drawn.
pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene) {
/// Inputs: view — the frame's texture view color attachment; scene — the scene whose entities are
/// drawn; aspect — the viewport aspect ratio (width/height), used to build the camera's perspective
/// projection.
///
/// Before drawing, the shared frame uniform buffer is rewritten from `scene.camera()` so the GPU
/// receives the active camera's view/projection matrices and position for this frame (Étape 4.3).
pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene, aspect: f32) {
self.write_frame_uniforms(scene.camera(), aspect);
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {