feat(examples): 3D MVP cube via standard shader, drop basic (Étape 5)

This commit is contained in:
Jérôme Bousquié
2026-09-17 10:40:43 +02:00
parent 43e8bfb40a
commit 0a85aff17b
15 changed files with 218 additions and 111 deletions
+35 -5
View File
@@ -55,6 +55,10 @@ pub struct Renderer {
/// entity label. Needed because `render_scene(&self, &Scene)` is immutable; the model matrix is
/// rewritten each frame for every entity.
object_cache: RefCell<HashMap<String, (wgpu::Buffer, wgpu::BindGroup)>>,
/// Flat (unlit) rendering flag, exposed via [`Renderer::set_unlit`]. When true, `options.x` of the
/// `FrameUniforms` is set to 1 so the `standard` shader returns vertex colors as-is — flat 2D
/// rendering is thus a special case of the 3D lit path (DRAFT Étape 5). Defaults to `false` (lit).
unlit: bool,
}
impl Renderer {
@@ -72,14 +76,12 @@ impl Renderer {
// Shared frame uniforms: identity camera + white directional light, lit mode by default.
// Values become meaningful once an active camera is wired (Étape 4.3); for now the default
// is a coherent scene when a shader actually reads them, and irrelevant to shaders that don't.
let default_frame = FrameUniforms::default();
let frame_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("frame uniform buffer"),
size: FRAME_UNIFORMS_SIZE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&frame_buffer, 0, bytemuck::bytes_of(&default_frame));
let frame_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("frame bind group"),
layout: &frame_layout,
@@ -109,7 +111,7 @@ impl Renderer {
}],
});
Self {
let renderer = Self {
queue,
device,
format,
@@ -118,7 +120,35 @@ impl Renderer {
frame_bind_group,
shared_object_bind_group,
object_cache: RefCell::new(HashMap::new()),
}
unlit: false,
};
// Seed the shared frame buffer with an identity camera + current unlit flag so the low-level
// `render` path (which has no window/camera) sees coherent values before `render_scene` runs.
renderer.write_default_frame_uniforms();
renderer
}
/// Writes the shared per-frame uniform buffer using an identity camera (view = proj = identity)
/// and the current [`Renderer::set_unlit`] flag. This is the initial state for the low-level
/// `render` path, which is independent of any window and therefore has no camera or aspect ratio.
/// Called at construction and whenever the renderer transitions between lit and unlit mode.
fn write_default_frame_uniforms(&self) {
let frame = FrameUniforms {
options: [if self.unlit { 1 } else { 0 }, 0, 0, 0],
..FrameUniforms::default()
};
self.queue
.write_buffer(&self.frame_buffer, 0, bytemuck::bytes_of(&frame));
}
/// Toggles flat (unlit) rendering. When true, the `standard` shader returns vertex colors as-is
/// (`options.x = 1`), so flat 2D rendering is a special case of the 3D lit path (DRAFT Étape 5 :
/// « 2D ⊂ 3D »). Rewrites the shared frame buffer immediately so the low-level `render` path picks
/// up the change ; the `render_scene` path reads the flag each frame in `write_frame_uniforms`.
/// Inputs: unlit — true for flat rendering, false (default) for Phong-lit rendering.
pub fn set_unlit(&mut self, unlit: bool) {
self.unlit = unlit;
self.write_default_frame_uniforms();
}
/// Rewrites the shared per-frame uniform buffer from the scene's active camera and the current
@@ -135,7 +165,7 @@ impl Renderer {
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],
options: [if self.unlit { 1 } else { 0 }, 0, 0, 0],
};
self.queue
.write_buffer(&self.frame_buffer, 0, bytemuck::bytes_of(&frame));