centralized state

This commit is contained in:
2026-06-13 13:44:02 +02:00
parent 0a8861a5b1
commit 48c499e356
11 changed files with 693 additions and 206 deletions
+142
View File
@@ -0,0 +1,142 @@
//! Szenen-Pass: zeichnet die 3D-Welt ins interne Target.
//!
//! Stand Schritt 3: ein hartkodierter Testwürfel mit Vertex-Colors —
//! Meshes aus OBJ und Texturen kommen in Schritt 5. Die Shader
//! (scene.wgsl) sind dagegen schon die echten PS1-Shader.
use wgpu::util::DeviceExt;
use crate::render::math::Mat4;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
pos: [f32; 3],
color: [f32; 3],
}
const VERTEX_LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: size_of::<Vertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
};
pub struct ScenePass {
pipeline: wgpu::RenderPipeline,
vbuf: wgpu::Buffer,
ibuf: wgpu::Buffer,
ubuf: wgpu::Buffer,
bind: wgpu::BindGroup,
index_count: u32,
}
impl ScenePass {
pub fn new(
device: &wgpu::Device,
color_format: wgpu::TextureFormat,
depth_format: wgpu::TextureFormat,
) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("scene"),
source: wgpu::ShaderSource::Wgsl(include_str!("scene.wgsl").into()),
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("scene"),
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[VERTEX_LAYOUT],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(color_format.into())],
}),
// Cull aus: der Z-Buffer sortiert auch so korrekt, und die
// irl3d-Materialien sind teils two-sided. Entscheidung pro
// Material fällt mit dem Szenen-Loader (Schritt 5).
primitive: wgpu::PrimitiveState::default(),
depth_stencil: Some(wgpu::DepthStencilState {
format: depth_format,
depth_write_enabled: Some(true),
depth_compare: Some(wgpu::CompareFunction::Less),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let (verts, indices) = cube();
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("cube vertices"),
contents: bytemuck::cast_slice(&verts),
usage: wgpu::BufferUsages::VERTEX,
});
let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("cube indices"),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsages::INDEX,
});
let ubuf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("scene uniforms"),
size: size_of::<Mat4>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("scene"),
layout: &pipeline.get_bind_group_layout(0),
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: ubuf.as_entire_binding(),
}],
});
Self { pipeline, vbuf, ibuf, ubuf, bind, index_count: indices.len() as u32 }
}
/// Uniforms für diesen Frame hochladen — vor dem Render-Pass rufen.
pub fn prepare(&self, queue: &wgpu::Queue, mvp: &Mat4) {
queue.write_buffer(&self.ubuf, 0, bytemuck::bytes_of(mvp));
}
pub fn draw(&self, pass: &mut wgpu::RenderPass) {
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.bind, &[]);
pass.set_vertex_buffer(0, self.vbuf.slice(..));
pass.set_index_buffer(self.ibuf.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..self.index_count, 0, 0..1);
}
}
/// Einheitswürfel um den Ursprung, jede Seite eine Farbe. Der
/// Helligkeitsverlauf über die Ecken erzeugt Gradienten, an denen
/// Dither und affine Interpolation sichtbar werden.
fn cube() -> (Vec<Vertex>, Vec<u16>) {
const S: f32 = 0.5;
let faces: [([f32; 3], [[f32; 3]; 4]); 6] = [
([0.9, 0.2, 0.2], [[ S, -S, -S], [ S, S, -S], [ S, S, S], [ S, -S, S]]), // +X
([0.2, 0.9, 0.9], [[-S, -S, -S], [-S, S, -S], [-S, S, S], [-S, -S, S]]), // -X
([0.2, 0.9, 0.2], [[-S, S, -S], [ S, S, -S], [ S, S, S], [-S, S, S]]), // +Y
([0.9, 0.2, 0.9], [[-S, -S, -S], [ S, -S, -S], [ S, -S, S], [-S, -S, S]]), // -Y
([0.3, 0.3, 0.9], [[-S, -S, S], [ S, -S, S], [ S, S, S], [-S, S, S]]), // +Z
([0.9, 0.8, 0.2], [[-S, -S, -S], [ S, -S, -S], [ S, S, -S], [-S, S, -S]]), // -Z
];
const SHADE: [f32; 4] = [1.0, 0.65, 0.4, 0.65];
let mut verts = Vec::with_capacity(24);
let mut idx: Vec<u16> = Vec::with_capacity(36);
for (base, corners) in faces {
let b = verts.len() as u16;
for (i, pos) in corners.into_iter().enumerate() {
verts.push(Vertex { pos, color: base.map(|c| c * SHADE[i]) });
}
idx.extend([b, b + 1, b + 2, b, b + 2, b + 3]);
}
(verts, idx)
}