From 4a674176cdd0a37b835f1902873d05423c998d27 Mon Sep 17 00:00:00 2001 From: BitSyndicate Date: Fri, 18 Apr 2025 23:45:11 +0200 Subject: [PATCH] feat: add obj model loading Co-authored-by: Chance --- flake.nix | 3 + shaders/shader.frag | 4 +- shaders/shader.vert | 2 +- src/main.rs | 161 ++++++++----------------------- src/model.rs | 57 ----------- src/model/mod.rs | 226 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 269 insertions(+), 184 deletions(-) delete mode 100644 src/model.rs create mode 100644 src/model/mod.rs diff --git a/flake.nix b/flake.nix index 65256e3..cf1dfe4 100644 --- a/flake.nix +++ b/flake.nix @@ -32,7 +32,10 @@ ]; }; buildInputs = with pkgs; [ + vulkan-tools + vulkan-tools-lunarg vulkan-loader + shaderc libGL wayland libxkbcommon diff --git a/shaders/shader.frag b/shaders/shader.frag index a669570..c9ffc77 100644 --- a/shaders/shader.frag +++ b/shaders/shader.frag @@ -1,8 +1,8 @@ #version 450 layout(location = 0) in vec2 tex_coords; -layout(set = 0, binding = 0) uniform texture2D t_diffuse; -layout(set = 0, binding = 1) uniform sampler s_diffuse; +layout(set = 1, binding = 0) uniform texture2D t_diffuse; +layout(set = 1, binding = 1) uniform sampler s_diffuse; layout(location = 0) out vec4 out_color; // layout(group = 0, binding = 0) out texture2D; void main() { diff --git a/shaders/shader.vert b/shaders/shader.vert index 300db84..79c95f0 100644 --- a/shaders/shader.vert +++ b/shaders/shader.vert @@ -4,7 +4,7 @@ layout(location = 0) in vec3 position; layout(location = 1) in vec3 color; layout(location = 3) in vec2 tex_coords; layout(location = 0) out vec2 tex_coord; -layout(set = 1, binding = 0) uniform UniformBufferObject { +layout(set = 0, binding = 0) uniform UniformBufferObject { mat4x4 projection; } view; void main() { diff --git a/src/main.rs b/src/main.rs index c085dac..54e5339 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ -use crate::model::parse_obj; use bytemuck::bytes_of; use image::EncodableLayout; use std::collections::BTreeMap; +use std::io::BufReader; use std::sync::Arc; use tobj::LoadOptions; use tracing::info; @@ -19,6 +19,7 @@ use zlog::config::LoggerConfig; pub mod camera; pub mod model; pub mod texture; + struct WindowContext<'window> { window: Arc, renderer: WgpuRenderer<'window>, @@ -37,17 +38,15 @@ struct WgpuRenderer<'surface> { surface: wgpu::Surface<'surface>, surface_config: wgpu::SurfaceConfiguration, render_pipeline: wgpu::RenderPipeline, - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - diffuse_bind_group: wgpu::BindGroup, - depth_texture: wgpu::Texture, depth_texture_view: wgpu::TextureView, camera: camera::Camera, camera_uniform: camera::CameraUniform, camera_buffer: wgpu::Buffer, camera_bind_group: wgpu::BindGroup, - index_count: u32, + pumpkin: model::Model, + + zenyx_logo: wgpu::BindGroup, } impl WgpuRenderer<'_> { @@ -82,12 +81,19 @@ impl WgpuRenderer<'_> { timestamp_writes: None, }); rpass.set_pipeline(&self.render_pipeline); - rpass.set_bind_group(0, &self.diffuse_bind_group, &[]); + rpass.set_bind_group(0, &self.camera_bind_group, &[]); - rpass.set_bind_group(1, &self.camera_bind_group, &[]); - rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - rpass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32); - rpass.draw_indexed(0..self.index_count, 0, 0..1); + for mesh in &self.pumpkin.meshes { + let bind_group = mesh + .material + .and_then(|i| self.pumpkin.materials.get(i)) + .map(|m| &m.bind_group) + .unwrap_or(&self.zenyx_logo); + rpass.set_bind_group(1, bind_group, &[]); + rpass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); + rpass.set_index_buffer(mesh.index_buffer.slice(..), IndexFormat::Uint32); + rpass.draw_indexed(0..mesh.num_elements, 0, 0..1); + } } self.queue.submit(Some(encoder.finish())); surface_texture.present() @@ -127,50 +133,7 @@ impl App<'_> { struct WgpuState { instance: wgpu::Instance, } -unsafe impl bytemuck::Pod for Vertex {} -unsafe impl bytemuck::Zeroable for Vertex {} -#[derive(Copy, Clone, Debug)] -#[repr(C)] -struct Vertex { - position: cgmath::Vector3, - color: cgmath::Vector3, - normal: cgmath::Vector3, - tex_coords: cgmath::Vector2, -} - -impl Vertex { - const ATTRIBS: [wgpu::VertexAttribute; 4] = [ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: std::mem::offset_of!(Vertex, color) as u64, - shader_location: 1, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: std::mem::offset_of!(Vertex, normal) as u64, - shader_location: 2, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: std::mem::offset_of!(Vertex, tex_coords) as u64, - shader_location: 3, - format: wgpu::VertexFormat::Float32x2, - }, - ]; - - const fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &Self::ATTRIBS, - } - } -} static ICON: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Badge.png")); static PUMPKIN: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/Pumpkin.obj")); @@ -244,7 +207,7 @@ impl WgpuState { struct ShaderCode([u8; N]); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Pipeline Layout"), - bind_group_layouts: &[&texture_bind_group_layout, &camera_bind_group_layout], + bind_group_layouts: &[&camera_bind_group_layout, &texture_bind_group_layout], push_constant_ranges: &[], }); @@ -279,7 +242,7 @@ impl WgpuState { vertex: wgpu::VertexState { module: &vert_shader, entry_point: Some("main"), - buffers: &[Vertex::desc()], + buffers: &[model::Vertex::desc()], compilation_options: Default::default(), }, primitive: wgpu::PrimitiveState { @@ -334,50 +297,32 @@ impl WgpuState { }; surface.configure(&device, &surface_config); - let pumpkin = tobj::load_obj("Pumpkin.obj", &tobj::GPU_LOAD_OPTIONS).unwrap(); - let (vertices, indices) = parse_obj(&pumpkin.0); - let (depth_texture, depth_texture_view) = - create_depth_texture(&device, surface_config.width, surface_config.height); - let materials = pumpkin.1.unwrap(); - - let diffuse_texture = - texture::Texture::from_bytes(&device, &queue, ICON, "zenyx-icon").unwrap(); - let model = tobj::load_obj_buf( - &mut PUMPKIN.as_bytes(), - &LoadOptions { - triangulate: true, - single_index: true, - ..Default::default() - }, - |_| Ok(Default::default()), - ) - .unwrap(); + let pumpkin = model::Model::load_obj( + &mut BufReader::new(std::fs::File::open("Pumpkin.obj").unwrap()), + &device, + &queue, + &texture_bind_group_layout, + ); - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex buffer"), - contents: bytemuck::cast_slice(vertices.as_ref()), - usage: BufferUsages::VERTEX, - }); - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index buffer"), - contents: bytemuck::cast_slice(indices.as_ref()), - usage: BufferUsages::INDEX, - }); + let zenyx_logo = texture::Texture::from_bytes(&device, &queue, ICON, "zenyx-icon").unwrap(); - let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + let zenyx_logo = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("zenyx-logo-diffuse"), layout: &texture_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + resource: wgpu::BindingResource::TextureView(&zenyx_logo.view), }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + resource: wgpu::BindingResource::Sampler(&zenyx_logo.sampler), }, ], - label: Some("diffuse_bind_group"), }); + + let (depth_texture, depth_texture_view) = + create_depth_texture(&device, surface_config.width, surface_config.height); let camera = camera::Camera { // position the camera 1 unit up and 2 units back // +z is out of the screen @@ -415,19 +360,17 @@ impl WgpuState { WgpuRenderer { surface, surface_config, - diffuse_bind_group, depth_texture, render_pipeline, device, - vertex_buffer, - index_buffer, camera, queue, camera_uniform, camera_buffer, camera_bind_group, - index_count: indices.len() as u32, depth_texture_view, + pumpkin, + zenyx_logo, } } @@ -460,37 +403,6 @@ fn create_depth_texture( (texture, view) } -static VERTICES: [Vertex; 4] = { - [ - Vertex { - position: cgmath::vec3(-0.5, -0.5, 0.0), - color: cgmath::vec3(1.0, 0.0, 0.0), - normal: cgmath::vec3(0.0, 0.0, 0.0), - tex_coords: cgmath::vec2(1.0, 0.0), - }, - Vertex { - position: cgmath::vec3(0.5, -0.5, 0.0), - color: cgmath::vec3(0.0, 1.0, 0.0), - normal: cgmath::vec3(0.0, 0.0, 0.0), - tex_coords: cgmath::vec2(0.0, 0.0), - }, - Vertex { - position: cgmath::vec3(0.5, 0.5, 0.0), - color: cgmath::vec3(0.0, 0.0, 1.0), - normal: cgmath::vec3(0.0, 0.0, 0.0), - tex_coords: cgmath::vec2(0.0, 1.0), - }, - Vertex { - position: cgmath::vec3(-0.5, 0.5, 0.0), - color: cgmath::vec3(0.0, 0.0, 1.0), - normal: cgmath::vec3(0.0, 0.0, 0.0), - tex_coords: cgmath::vec2(1.0, 1.0), - }, - ] -}; - -static INDICIES: [u32; 6] = [0, 1, 2, 2, 3, 0]; - impl ApplicationHandler for App<'_> { fn window_event( &mut self, @@ -535,7 +447,8 @@ impl ApplicationHandler for App<'_> { let mut new_config = window_ctx.renderer.surface_config.clone(); new_config.width = size.width; new_config.height = size.height; - let (depth_texture, depth_view) = create_depth_texture(&window_ctx.renderer.device, size.width, size.height); + let (depth_texture, depth_view) = + create_depth_texture(&window_ctx.renderer.device, size.width, size.height); window_ctx.renderer.depth_texture = depth_texture; window_ctx.renderer.depth_texture_view = depth_view; diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index d64f875..0000000 --- a/src/model.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::Vertex; -use crate::texture::Texture; -use cgmath::{Vector2, Vector3, Zero}; -use tobj::Model; - -pub struct Material { - pub name: String, - pub diffuse_texture: Texture, - pub bind_group: wgpu::BindGroup, -} - -pub struct Mesh { - pub name: String, - pub vertex_buffer: wgpu::Buffer, - pub index_buffer: wgpu::Buffer, - pub num_elements: u32, - pub material: usize, -} - -pub fn parse_obj(obj: &Vec) -> (Vec, Vec) { - let mut combined_vertices = Vec::new(); - let mut combined_indices = Vec::new(); - let mut vertex_offset = 0; - - for object in obj { - let mesh: &_ = &object.mesh; - let vertices: Vec = (0..mesh.positions.len() / 3) - .map(|i| Vertex { - position: Vector3::from([ - mesh.positions[i * 3], - mesh.positions[i * 3 + 1], - mesh.positions[i * 3 + 2], - ]), - color: cgmath::Vector3::from([1.0, 1.0, 1.0]), - normal: if !mesh.normals.is_empty() { - Vector3::from([ - mesh.normals[i * 3], - mesh.normals[i * 3 + 1], - mesh.normals[i * 3 + 2], - ]) - } else { - Vector3::zero() - }, - tex_coords: if !mesh.texcoords.is_empty() { - Vector2::from([mesh.texcoords[i * 2], mesh.texcoords[i * 2 + 1]]) - } else { - Vector2::zero() - }, - }) - .collect(); - combined_vertices.extend(vertices); - combined_indices.extend(mesh.indices.iter().map(|&index| index + vertex_offset)); - vertex_offset += (mesh.positions.len() as u32) / 3; - } - - (combined_vertices, combined_indices) -} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..1309cff --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,226 @@ +use std::io::{BufRead, BufReader}; + +use crate::texture::Texture; +use cgmath::{Vector2, Vector3, Zero}; +use tobj::Model as tModel; +use wgpu::util::DeviceExt; + +pub struct Model { + pub meshes: Vec, + pub materials: Vec, +} + +pub struct Material { + pub name: String, + pub diffuse_texture: Texture, + pub bind_group: wgpu::BindGroup, +} + +pub struct Mesh { + pub name: String, + pub vertex_buffer: wgpu::Buffer, + pub index_buffer: wgpu::Buffer, + pub num_elements: u32, + pub material: Option, +} + +impl Model { + pub fn load_obj( + read: &mut R, + device: &wgpu::Device, + queue: &wgpu::Queue, + layout: &wgpu::BindGroupLayout, + ) -> Self { + let (models, obj_materials) = tobj::load_obj_buf( + read, + &tobj::LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }, + |p| tobj::load_mtl_buf(&mut BufReader::new(std::fs::File::open(p).unwrap())), + ) + .unwrap(); + + let mut materials = Vec::new(); + + for m in obj_materials.unwrap() { + let Some(texture) = &m.diffuse_texture else { + continue; + }; + let diffuse_texture = load_texture(&m.name, texture, device, queue); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("model-texture-bind"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + ], + }); + + materials.push(Material { + name: m.name, + diffuse_texture, + bind_group, + }); + } + + let meshes = models + .into_iter() + .map(|m| { + let vertices = (0..m.mesh.positions.len() / 3) + .map(|i| { + let position = cgmath::vec3( + m.mesh.positions[i * 3], + m.mesh.positions[i * 3 + 1], + m.mesh.positions[i * 3 + 2], + ); + let tex_coords = cgmath::vec2( + m.mesh.texcoords[i * 2], + 1.0 - m.mesh.texcoords[i * 2 + 1], + ); + if m.mesh.normals.is_empty() { + Vertex { + position, + tex_coords, + normal: cgmath::Vector3::zero(), + color: cgmath::Vector3::zero(), + } + } else { + Vertex { + position, + tex_coords, + normal: cgmath::vec3( + m.mesh.normals[i * 3], + m.mesh.normals[i * 3 + 1], + m.mesh.normals[i * 3 + 2], + ), + color: cgmath::Vector3::zero(), + } + } + }) + .collect::>(); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&m.mesh.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + Mesh { + name: "TOBJ_MESH".to_string(), + vertex_buffer, + index_buffer, + num_elements: m.mesh.indices.len() as u32, + material: m.mesh.material_id, + } + }) + .collect::>(); + + Self { meshes, materials } + } +} + +pub fn load_texture>( + label: &str, + path: S, + device: &wgpu::Device, + queue: &wgpu::Queue, +) -> Texture { + let file = std::fs::read(path).unwrap(); + Texture::from_bytes(device, queue, &file, label).unwrap() +} + +pub fn load(obj: &[tModel]) -> (Vec, Vec) { + let mut combined_vertices = Vec::new(); + let mut combined_indices = Vec::new(); + let mut vertex_offset = 0; + + for object in obj { + let mesh: &_ = &object.mesh; + let vertices: Vec = (0..mesh.positions.len() / 3) + .map(|i| Vertex { + position: Vector3::from([ + mesh.positions[i * 3], + mesh.positions[i * 3 + 1], + mesh.positions[i * 3 + 2], + ]), + color: cgmath::Vector3::from([1.0, 1.0, 1.0]), + normal: if !mesh.normals.is_empty() { + Vector3::from([ + mesh.normals[i * 3], + mesh.normals[i * 3 + 1], + mesh.normals[i * 3 + 2], + ]) + } else { + Vector3::zero() + }, + tex_coords: if !mesh.texcoords.is_empty() { + Vector2::from([mesh.texcoords[i * 2], mesh.texcoords[i * 2 + 1]]) + } else { + Vector2::zero() + }, + }) + .collect(); + combined_vertices.extend(vertices); + combined_indices.extend(mesh.indices.iter().map(|&index| index + vertex_offset)); + vertex_offset += (mesh.positions.len() as u32) / 3; + } + + (combined_vertices, combined_indices) +} + +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct Vertex { + pub position: cgmath::Vector3, + pub color: cgmath::Vector3, + pub normal: cgmath::Vector3, + pub tex_coords: cgmath::Vector2, +} + +unsafe impl bytemuck::Pod for Vertex {} +unsafe impl bytemuck::Zeroable for Vertex {} +impl Vertex { + pub const ATTRIBS: [wgpu::VertexAttribute; 4] = [ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: std::mem::offset_of!(Vertex, color) as u64, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: std::mem::offset_of!(Vertex, normal) as u64, + shader_location: 2, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: std::mem::offset_of!(Vertex, tex_coords) as u64, + shader_location: 3, + format: wgpu::VertexFormat::Float32x2, + }, + ]; + + pub const fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &Self::ATTRIBS, + } + } +}