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:
+7
-1
@@ -125,8 +125,14 @@ impl App {
|
||||
/// Called automatically each frame by the default `AppHandler::render`, or manually by users
|
||||
/// who override `render` to control drawing themselves.
|
||||
/// Inputs: view — the frame's texture view acting as the color attachment target.
|
||||
///
|
||||
/// The viewport aspect ratio (needed for the active camera's perspective projection, Étape 4.3)
|
||||
/// is derived here from the window's current inner size, so the `Renderer` stays independent of
|
||||
/// the windowing backend.
|
||||
pub fn render_scene(&self, view: &wgpu::TextureView) {
|
||||
self.renderer().render_scene(view, &self.scene);
|
||||
let size = self.window().inner_size();
|
||||
let aspect = size.width as f32 / size.height.max(1) as f32;
|
||||
self.renderer().render_scene(view, &self.scene, aspect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+45
-11
@@ -15,9 +15,18 @@
|
||||
|
||||
use glam::{Mat4, Vec3};
|
||||
|
||||
/// Default vertical field of view in radians (45°).
|
||||
pub const DEFAULT_FOV: f32 = 45.0_f32.to_radians();
|
||||
/// Near clipping plane distance used by the default perspective projection.
|
||||
pub const DEFAULT_NEAR: f32 = 0.1;
|
||||
/// Far clipping plane distance used by the default perspective projection.
|
||||
pub const DEFAULT_FAR: f32 = 100.0;
|
||||
|
||||
/// Represents a 3D camera for viewing the scene.
|
||||
///
|
||||
/// The camera defines the viewpoint and projection settings for rendering.
|
||||
/// The camera defines the viewpoint (position/target/up), the projection parameters (fov, near, far)
|
||||
/// and can produce the view and projection matrices uploaded each frame to the `FrameUniforms` buffer
|
||||
/// (Étape 4.3). Use `Scene::set_camera` to install it as the scene's active camera.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Camera {
|
||||
/// Position of the camera in world space
|
||||
@@ -26,37 +35,62 @@ pub struct Camera {
|
||||
pub target: Vec3,
|
||||
/// Up vector defining the camera's orientation
|
||||
pub up: Vec3,
|
||||
/// Vertical field of view in radians (used by the perspective projection).
|
||||
pub fov: f32,
|
||||
/// Near clipping plane distance (used by the perspective projection).
|
||||
pub near: f32,
|
||||
/// Far clipping plane distance (used by the perspective projection).
|
||||
pub far: f32,
|
||||
}
|
||||
|
||||
impl Default for Camera {
|
||||
/// Default camera : positioned at (0, 0, 3) looking at the origin with a 45° vertical fov,
|
||||
/// near 0.1 and far 100. Good enough to frame a unit-cube scene out of the box.
|
||||
fn default() -> Self {
|
||||
Self::new(Vec3::new(0.0, 0.0, 3.0), Vec3::ZERO, Vec3::Y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
/// Creates a new camera with specified position, target, and up vector.
|
||||
/// Creates a new perspective camera with the default fov/near/far.
|
||||
/// Inputs: position (world-space eye point), target (world-space look-at point), up (view up vector).
|
||||
/// Adjust the projection via [`Camera::with_perspective`] if the defaults don't fit.
|
||||
pub fn new(position: Vec3, target: Vec3, up: Vec3) -> Self {
|
||||
Self {
|
||||
position,
|
||||
target,
|
||||
up,
|
||||
fov: DEFAULT_FOV,
|
||||
near: DEFAULT_NEAR,
|
||||
far: DEFAULT_FAR,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the perspective projection parameters and returns the camera for chaining.
|
||||
/// Inputs: fov (vertical field of view in radians), near (near plane), far (far plane).
|
||||
pub fn with_perspective(mut self, fov: f32, near: f32, far: f32) -> Self {
|
||||
self.fov = fov;
|
||||
self.near = near;
|
||||
self.far = far;
|
||||
self
|
||||
}
|
||||
|
||||
/// Computes the view matrix for this camera.
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Mat4` representing the view transformation matrix
|
||||
/// A `Mat4` representing the view transformation matrix (world → view space)
|
||||
pub fn view_matrix(&self) -> Mat4 {
|
||||
glam::camera::rh::view::look_at_mat4(self.position, self.target, self.up)
|
||||
}
|
||||
|
||||
/// Computes the projection matrix for this camera.
|
||||
/// Computes the perspective projection matrix for this camera using its stored fov/near/far.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `fov`: Field of view in radians
|
||||
/// - `aspect`: Aspect ratio of the viewport
|
||||
/// - `near`: Near clipping plane distance
|
||||
/// - `far`: Far clipping plane distance
|
||||
/// - `aspect`: Aspect ratio of the viewport (width / height)
|
||||
///
|
||||
/// # Returns
|
||||
/// A `Mat4` representing the projection transformation matrix
|
||||
pub fn projection_matrix(&self, fov: f32, aspect: f32, near: f32, far: f32) -> Mat4 {
|
||||
glam::camera::rh::proj::opengl::perspective(fov, aspect, near, far)
|
||||
/// A `Mat4` representing the projection transformation matrix (view → clip space)
|
||||
pub fn projection_matrix(&self, aspect: f32) -> Mat4 {
|
||||
glam::camera::rh::proj::opengl::perspective(self.fov, aspect, self.near, self.far)
|
||||
}
|
||||
}
|
||||
|
||||
+24
-3
@@ -11,13 +11,14 @@
|
||||
//! - **Ergonomie**: Users interact only with entity-level operations (add/remove/get) rather than wgpu buffers/pipelines directly.
|
||||
|
||||
use crate::math::Transform;
|
||||
use crate::resources::{Material, Mesh};
|
||||
use crate::resources::{Camera, Material, Mesh};
|
||||
use crate::scene::Entity;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Resource depot and entity graph. Stores Meshes and Materials keyed by identifier strings,
|
||||
/// and maps entity labels to their associated `Entity` (mesh + material + transform) for rendering iteration.
|
||||
/// maps entity labels to their associated `Entity` (mesh + material + transform) for rendering iteration,
|
||||
/// and holds the scene's active `Camera` used to build the per-frame view/projection matrices (Étape 4.3).
|
||||
/// Created once during application setup; entities are added before the render loop starts.
|
||||
pub struct Scene {
|
||||
/// Map of mesh identifiers to owned `Arc<Mesh>` instances. Populated via `add_mesh()`.
|
||||
@@ -26,19 +27,39 @@ pub struct Scene {
|
||||
materials: HashMap<String, Arc<Material>>,
|
||||
/// Map of entity labels to `Entity` associations. Populated via `add_entity()` / `add_entity_with_transform()`.
|
||||
entities: HashMap<String, Entity>,
|
||||
/// Active camera used for rendering. Read each frame by `Renderer::render_scene` to compute the
|
||||
/// view/projection matrices written into the frame uniform buffer. Replaced via `set_camera()`.
|
||||
camera: Camera,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
/// Creates an empty scene with no registered resources or entities.
|
||||
/// Creates an empty scene with no registered resources or entities and a default camera
|
||||
/// (`Camera::default()` : position (0,0,3), looking at origin, 45° perspective).
|
||||
/// Called at application startup before any resource registration.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
meshes: HashMap::new(),
|
||||
materials: HashMap::new(),
|
||||
entities: HashMap::new(),
|
||||
camera: Camera::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the scene's active camera. The new camera is used from the next frame onward by
|
||||
/// `Renderer::render_scene` to build the view/projection matrices and the camera position.
|
||||
/// Inputs: camera — the new camera configuration. Call during setup or `AppHandler::update`
|
||||
/// to move/re-orient the view (e.g. orbit or FPS controls).
|
||||
pub fn set_camera(&mut self, camera: Camera) {
|
||||
self.camera = camera;
|
||||
}
|
||||
|
||||
/// Returns a reference to the scene's active camera.
|
||||
/// Called by users to read the current camera (e.g. to move it based on input) and internally by
|
||||
/// `Renderer::render_scene` to upload its matrices.
|
||||
pub fn camera(&self) -> &Camera {
|
||||
&self.camera
|
||||
}
|
||||
|
||||
/// Registers a Mesh in the scene under a unique identifier.
|
||||
/// Inputs: id (unique key), mesh (Arc-wrapped Mesh instance). Returns Ok(id) on success or Err(String) if already exists.
|
||||
/// Called during scene initialization when building the resource depot.
|
||||
|
||||
Reference in New Issue
Block a user