idk shitload of stuff
This commit is contained in:
+142
-57
@@ -1,40 +1,74 @@
|
||||
//! 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.
|
||||
//! Stand Schritt 5: Geometrie + Texturen kommen vom Aufrufer (Brush-Welt
|
||||
//! aus der `.map`, siehe render::brush; Bilder aus engine::tga). Pro Textur
|
||||
//! eine Bind-Group + ein Draw-Batch (Material-Batching „pro Textur ein
|
||||
//! Draw" wie im Plan). Die Shader (scene.wgsl) sind die echten PS1-Shader.
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::engine::tga::Image;
|
||||
use crate::render::math::Mat4;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Vertex {
|
||||
pos: [f32; 3],
|
||||
color: [f32; 3],
|
||||
pub(crate) struct Vertex {
|
||||
pub(crate) pos: [f32; 3],
|
||||
pub(crate) uv: [f32; 2],
|
||||
}
|
||||
|
||||
/// Ein Draw-Batch: ein zusammenhängender Index-Bereich, der mit *einer*
|
||||
/// Textur gezeichnet wird.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Batch {
|
||||
pub(crate) texture: usize,
|
||||
pub(crate) start: u32,
|
||||
pub(crate) count: u32,
|
||||
}
|
||||
|
||||
/// CPU-seitige Welt-Geometrie, fertig zum Hochladen. Die Indizes sind nach
|
||||
/// Textur gruppiert; `batches` zeigt in diese Reihenfolge.
|
||||
pub(crate) struct Mesh {
|
||||
pub(crate) verts: Vec<Vertex>,
|
||||
pub(crate) indices: Vec<u32>,
|
||||
pub(crate) batches: Vec<Batch>,
|
||||
}
|
||||
|
||||
/// Spiegelt das `Uniforms`-Struct in scene.wgsl. `_pad` rundet die Größe
|
||||
/// auf 80 Byte (16er-Vielfaches), wie es die Uniform-Adressraum-Regeln
|
||||
/// von WGSL verlangen.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
mvp: [[f32; 4]; 4],
|
||||
half_res: [f32; 2],
|
||||
_pad: [f32; 2],
|
||||
}
|
||||
|
||||
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],
|
||||
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2],
|
||||
};
|
||||
|
||||
pub struct ScenePass {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
vbuf: wgpu::Buffer,
|
||||
ibuf: wgpu::Buffer,
|
||||
ubuf: wgpu::Buffer,
|
||||
bind: wgpu::BindGroup,
|
||||
index_count: u32,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
vbuf: wgpu::Buffer,
|
||||
ibuf: wgpu::Buffer,
|
||||
ubuf: wgpu::Buffer,
|
||||
uniform_bind: wgpu::BindGroup, // group 0
|
||||
tex_binds: Vec<wgpu::BindGroup>, // group 1, pro Textur
|
||||
batches: Vec<Batch>,
|
||||
}
|
||||
|
||||
impl ScenePass {
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
color_format: wgpu::TextureFormat,
|
||||
depth_format: wgpu::TextureFormat,
|
||||
mesh: &Mesh,
|
||||
images: &[Image],
|
||||
) -> Self {
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("scene"),
|
||||
@@ -55,10 +89,16 @@ impl ScenePass {
|
||||
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(),
|
||||
// Back-face Culling: Brush-Polygone werden CCW um ihre nach
|
||||
// außen zeigende Normale gewickelt (siehe render::brush), und
|
||||
// die Z-up→Y-up-Drehung erhält die Orientierung (det +1). Also
|
||||
// sind die Außenflächen front-facing — Rückseiten und alle
|
||||
// verdeckten Innenflächen zwischen Brushes fallen weg.
|
||||
// (Front-Face bleibt der wgpu-Default CCW.)
|
||||
primitive: wgpu::PrimitiveState {
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: depth_format,
|
||||
depth_write_enabled: Some(true),
|
||||
@@ -71,25 +111,24 @@ impl ScenePass {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let (verts, indices) = cube();
|
||||
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("cube vertices"),
|
||||
contents: bytemuck::cast_slice(&verts),
|
||||
label: Some("scene vertices"),
|
||||
contents: bytemuck::cast_slice(&mesh.verts),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let ibuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("cube indices"),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
label: Some("scene indices"),
|
||||
contents: bytemuck::cast_slice(&mesh.indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
let ubuf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("scene uniforms"),
|
||||
size: size_of::<Mat4>() as u64,
|
||||
size: size_of::<Uniforms>() 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"),
|
||||
let uniform_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("scene uniforms"),
|
||||
layout: &pipeline.get_bind_group_layout(0),
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
@@ -97,46 +136,92 @@ impl ScenePass {
|
||||
}],
|
||||
});
|
||||
|
||||
Self { pipeline, vbuf, ibuf, ubuf, bind, index_count: indices.len() as u32 }
|
||||
// Nearest-Sampler: harte Texel, kein Filtering — PS1.
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("scene nearest"),
|
||||
address_mode_u: wgpu::AddressMode::Repeat,
|
||||
address_mode_v: wgpu::AddressMode::Repeat,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
let tex_layout = pipeline.get_bind_group_layout(1);
|
||||
let tex_binds = images.iter()
|
||||
.map(|img| upload_texture(device, queue, &tex_layout, &sampler, img))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
pipeline, vbuf, ibuf, ubuf, uniform_bind, tex_binds,
|
||||
batches: mesh.batches.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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));
|
||||
/// `half_res` = halbe interne Auflösung (fürs Pixel-Snap im Shader).
|
||||
pub fn prepare(&self, queue: &wgpu::Queue, mvp: &Mat4, half_res: [f32; 2]) {
|
||||
let u = Uniforms { mvp: mvp.0, half_res, _pad: [0.0; 2] };
|
||||
queue.write_buffer(&self.ubuf, 0, bytemuck::bytes_of(&u));
|
||||
}
|
||||
|
||||
pub fn draw(&self, pass: &mut wgpu::RenderPass) {
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.bind, &[]);
|
||||
pass.set_bind_group(0, &self.uniform_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]) });
|
||||
pass.set_index_buffer(self.ibuf.slice(..), wgpu::IndexFormat::Uint32);
|
||||
for b in &self.batches {
|
||||
pass.set_bind_group(1, &self.tex_binds[b.texture], &[]);
|
||||
pass.draw_indexed(b.start..b.start + b.count, 0, 0..1);
|
||||
}
|
||||
idx.extend([b, b + 1, b + 2, b, b + 2, b + 3]);
|
||||
}
|
||||
(verts, idx)
|
||||
}
|
||||
|
||||
/// Ein RGBA8-`Image` als GPU-Textur hochladen und die zugehörige
|
||||
/// Bind-Group (Textur + Sampler, group 1) bauen.
|
||||
fn upload_texture(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
sampler: &wgpu::Sampler,
|
||||
img: &Image,
|
||||
) -> wgpu::BindGroup {
|
||||
let size = wgpu::Extent3d {
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("scene texture"),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
// Nicht-sRGB: die Quantisierung im Shader erwartet rohe Werte.
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&img.rgba,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(img.width * 4),
|
||||
rows_per_image: Some(img.height),
|
||||
},
|
||||
size,
|
||||
);
|
||||
let view = texture.create_view(&Default::default());
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("scene texture"),
|
||||
layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view) },
|
||||
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(sampler) },
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user