From acb7c13a5f3151ff86c705fa93b1b26920a88b2e Mon Sep 17 00:00:00 2001 From: Verox001 Date: Tue, 20 May 2025 02:44:41 +0200 Subject: [PATCH] Complete rewrite --- simulator/src/main.rs | 45 +++- solar_engine/src/application.rs | 19 +- .../src/{state.rs => engine_state.rs} | 57 +++-- solar_engine/src/geometry_manager.rs | 24 +-- .../src/{globals.rs => globals_manager.rs} | 33 +-- solar_engine/src/instance_manager.rs | 96 +++++++-- solar_engine/src/lib.rs | 21 +- .../src/{light.rs => light_manager.rs} | 26 ++- solar_engine/src/material.rs | 58 ------ solar_engine/src/material_manager.rs | 110 ++++++++++ .../src/{renderer.rs => render_manager.rs} | 197 ++++++------------ solar_engine/src/shaders/shader.wgsl | 75 +++---- 12 files changed, 406 insertions(+), 355 deletions(-) rename solar_engine/src/{state.rs => engine_state.rs} (83%) rename solar_engine/src/{globals.rs => globals_manager.rs} (80%) rename solar_engine/src/{light.rs => light_manager.rs} (94%) delete mode 100644 solar_engine/src/material.rs create mode 100644 solar_engine/src/material_manager.rs rename solar_engine/src/{renderer.rs => render_manager.rs} (54%) diff --git a/simulator/src/main.rs b/simulator/src/main.rs index fd1eaa1..c2e9506 100644 --- a/simulator/src/main.rs +++ b/simulator/src/main.rs @@ -1,5 +1,5 @@ use cgmath::{Rotation3, Vector3}; -use solar_engine::{Application, Body, InputEvent, Key, Light, MouseButton, RenderInstance, Shape, Simulator}; +use solar_engine::{Application, Body, GpuMaterial, InputEvent, Key, Light, MouseButton, RenderInstance, Shape, Simulator, SubInstance}; use std::sync::{Arc, RwLock}; use std::thread; @@ -69,27 +69,52 @@ pub async fn run() { 5.0, 1.0 / 1000.0, )); + + let sun_material = GpuMaterial::new( + [1.0, 1.0, 0.0, 1.0], + [2.0, 2.0, 1.0], + ); + + let earth_material = GpuMaterial::new( + [0.0, 0.0, 1.0, 1.0], + [0.0, 0.0, 0.0], + ); + + let default_material = GpuMaterial::new( + [0.5, 0.5, 0.5, 1.0], + [0.0, 0.0, 0.0], + ); + + state.set_materials(vec![ + sun_material, + earth_material, + default_material, + ]); let instances = bodies .iter() .enumerate() .map(|(i, b)| { + let material_id = match i { + 0 => 0, + 1 => 1, + _ => 2, + }; + RenderInstance { position: ((b.position / 1.496e11) - sun_pos).cast::().unwrap(), rotation: cgmath::Quaternion::from_angle_z(cgmath::Deg(0.0)), - color: match i { - 0 => [1.0, 1.0, 0.0], // Sun - 1 => [0.0, 0.0, 1.0], // Earth - _ => [0.5, 0.5, 0.5], - }, scale: 0.05, - shape: Shape::Sphere, - always_lit: i == 0, // Sun - is_transparent: false, + submeshes: vec![ + SubInstance { + shape: Shape::Sphere, + material_id, + } + ], } }) .collect(); - + state.set_instances(instances); }) .on_input({ diff --git a/solar_engine/src/application.rs b/solar_engine/src/application.rs index c7b84d5..5b57f4f 100644 --- a/solar_engine/src/application.rs +++ b/solar_engine/src/application.rs @@ -5,10 +5,10 @@ use winit::window::{Window, WindowId}; use crate::input::{InputEvent, InputTracker}; pub struct StateApplication<'a> { - state: Option>, + state: Option>, modifiers: Modifiers, - update_fn: Option) + 'a>>, - input_fn: Option, &InputEvent) + 'a>>, + update_fn: Option) + 'a>>, + input_fn: Option, &InputEvent) + 'a>>, input_tracker: InputTracker } @@ -17,12 +17,12 @@ impl<'a> StateApplication<'a> { Self { state: None, update_fn: None, input_fn: None, modifiers: Modifiers::default(), input_tracker: InputTracker::default() } } - pub fn on_update) + 'a>(mut self, func: F) -> Self { + pub fn on_update) + 'a>(mut self, func: F) -> Self { self.update_fn = Some(Box::new(func)); self } - pub fn on_input, &InputEvent) + 'a>(mut self, func: F) -> Self { + pub fn on_input, &InputEvent) + 'a>(mut self, func: F) -> Self { self.input_fn = Some(Box::new(func)); self } @@ -38,7 +38,7 @@ impl<'a> ApplicationHandler for StateApplication<'a> { let window = event_loop .create_window(Window::default_attributes().with_title("Solar Engine")) .unwrap(); - self.state = Some(crate::state::State::new(window)); + self.state = Some(crate::engine_state::EngineState::new(window)); } fn window_event( @@ -58,8 +58,11 @@ impl<'a> ApplicationHandler for StateApplication<'a> { self.state.as_mut().unwrap().resize(physical_size); } WindowEvent::RedrawRequested => { - if let (Some(state), Some(update_fn)) = (self.state.as_mut(), self.update_fn.as_mut()) { - update_fn(state); + if let Some(state) = self.state.as_mut() { + state.update(); + if let Some(update_fn) = self.update_fn.as_mut() { + update_fn(state); + } } self.state.as_mut().unwrap().render().unwrap(); diff --git a/solar_engine/src/state.rs b/solar_engine/src/engine_state.rs similarity index 83% rename from solar_engine/src/state.rs rename to solar_engine/src/engine_state.rs index cab7f34..75e07ea 100644 --- a/solar_engine/src/state.rs +++ b/solar_engine/src/engine_state.rs @@ -6,13 +6,14 @@ use winit::dpi::PhysicalSize; use winit::window::{Window}; use crate::camera::Camera; use crate::geometry_manager::GeometryManager; -use crate::globals::GlobalsManager; -use crate::instance_manager::InstanceManager; -use crate::light::{LightManager}; -use crate::material::{GpuMaterial, MaterialManager}; -use crate::renderer::{RenderInstance, Renderer}; +use crate::globals_manager::GlobalsManager; +use crate::instance_manager::{InstanceManager, RenderInstance}; +use crate::light_manager::{LightManager}; +use crate::material_manager::{GpuMaterial, MaterialManager}; +use crate::render_manager::{RenderManager}; pub struct SampleCount(pub u32); +type RenderResult = Result<(), SurfaceError>; impl SampleCount { pub fn get(&self) -> u32 { @@ -20,7 +21,7 @@ impl SampleCount { } } -pub struct State<'a> { +pub struct EngineState<'a> { surface: Surface<'a>, device: Device, queue: Queue, @@ -31,15 +32,15 @@ pub struct State<'a> { window: Arc, pub camera: Camera, - pub globals: GlobalsManager, + pub globals_manager: GlobalsManager, pub geometry_manager: GeometryManager, pub instance_manager: InstanceManager, pub light_manager: LightManager, pub material_manager: MaterialManager, - pub renderer: Renderer, + pub render_manager: RenderManager, } -impl<'a> State<'a> { +impl<'a> EngineState<'a> { pub(crate) fn new(window: Window) -> Self { let window = Arc::new(window); let size = window.inner_size(); @@ -61,27 +62,17 @@ impl<'a> State<'a> { let camera = Camera::new(config.width as f32 / config.height as f32); - let globals = GlobalsManager::new(&device, config.width, config.height, &camera); + let globals_manager = GlobalsManager::new(&device, config.width, config.height, &camera); let geometry_manager = GeometryManager::new(&device); let instance_manager = InstanceManager::new(&device); let mut light_manager = LightManager::new(&device, 100); - let initial_materials = vec![ - GpuMaterial { - albedo: [1.0, 1.0, 1.0], - emissive: [0.0, 0.0, 0.0], - metallic: 0.0, - roughness: 0.5, - }; - 8 - ]; + let mut material_manager = MaterialManager::new(&device); - let mut material_manager = MaterialManager::new(&device, initial_materials); - - let renderer = Renderer::new( + let render_manager = RenderManager::new( &device, &config, - globals.layout(), + &globals_manager, &mut light_manager, &material_manager, &camera, @@ -97,12 +88,12 @@ impl<'a> State<'a> { size, window, camera, - globals, + globals_manager, geometry_manager, instance_manager, light_manager, material_manager, - renderer, + render_manager, } } @@ -189,22 +180,24 @@ impl<'a> State<'a> { self.surface.configure(&self.device, &self.config); self.camera.set_aspect(new_size.width as f32 / new_size.height as f32); - self.globals.resize(new_size.width, new_size.height); - self.renderer.resize(&self.device, new_size.width, new_size.height); + self.globals_manager.resize(&self.queue, new_size.width, new_size.height, &self.camera); + self.render_manager.resize(&self.device, new_size.width, new_size.height); } } - pub fn render(&mut self) -> Result<(), SurfaceError> { + pub fn update(&mut self) {} + + pub fn render(&mut self) -> RenderResult { let output = self.surface.get_current_texture()?; let view = output.texture.create_view(&Default::default()); - self.renderer.render_frame( + self.render_manager.render_frame( &self.device, &self.queue, output, &view, self.config.format, - &mut self.globals, + &mut self.globals_manager, &self.camera, &mut self.light_manager, &self.geometry_manager, @@ -213,6 +206,10 @@ impl<'a> State<'a> { ) } + pub fn set_materials(&mut self, materials: Vec) { + self.material_manager.set_materials(&self.device, materials); + } + pub fn set_instances(&mut self, instances: Vec) { self.instance_manager.set_instances(&self.device, &self.queue, instances); } diff --git a/solar_engine/src/geometry_manager.rs b/solar_engine/src/geometry_manager.rs index e515a6b..79ed1c8 100644 --- a/solar_engine/src/geometry_manager.rs +++ b/solar_engine/src/geometry_manager.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use wgpu::{Device, Buffer}; use wgpu::util::DeviceExt; -use crate::renderer::Vertex; +use crate::render_manager::Vertex; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Shape { @@ -24,21 +24,19 @@ impl GeometryManager { let mut geometries = HashMap::new(); // Circle - let (circle_vertices, circle_indices) = create_circle_vertices(512, 0.5, [0.5, 0.5, 0.5]); + let (circle_vertices, circle_indices) = generate_circle_mesh(512, 0.5); geometries.insert( Shape::Circle, Self::create_geometry(device, &circle_vertices, &circle_indices), ); // Sphere - let (sphere_vertices, sphere_indices) = create_sphere_vertices(32, 32, 0.5, [0.5, 0.5, 0.5]); + let (sphere_vertices, sphere_indices) = generate_sphere_mesh(32, 32, 0.5); geometries.insert( Shape::Sphere, Self::create_geometry(device, &sphere_vertices, &sphere_indices), ); - - // Füge hier beliebige weitere Shapes hinzu - + Self { geometries } } @@ -71,10 +69,10 @@ impl GeometryManager { } } -pub fn create_circle_vertices(segment_count: usize, radius: f32, color: [f32; 3]) -> (Vec, Vec) { +pub fn generate_circle_mesh(segment_count: usize, radius: f32) -> (Vec, Vec) { let mut vertices = vec![Vertex { position: [0.0, 0.0, 0.0], - color, + color: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0], }]; let mut indices = vec![]; @@ -85,7 +83,7 @@ pub fn create_circle_vertices(segment_count: usize, radius: f32, color: [f32; 3] let y = radius * theta.sin(); vertices.push(Vertex { position: [x, y, 0.0], - color, + color: [1.0, 1.0, 1.0], normal: [0.0, 0.0, 1.0], }); } @@ -99,7 +97,7 @@ pub fn create_circle_vertices(segment_count: usize, radius: f32, color: [f32; 3] (vertices, indices) } -pub fn create_sphere_vertices(stacks: usize, slices: usize, radius: f32, color: [f32; 3]) -> (Vec, Vec) { +pub fn generate_sphere_mesh(stacks: usize, slices: usize, radius: f32) -> (Vec, Vec) { let mut vertices = Vec::new(); let mut indices = Vec::new(); @@ -113,12 +111,10 @@ pub fn create_sphere_vertices(stacks: usize, slices: usize, radius: f32, color: let x = r * theta.cos(); let z = r * theta.sin(); - let normal = [x, y, z]; - vertices.push(Vertex { position: [x * radius, y * radius, z * radius], - color, - normal, + color: [1.0, 1.0, 1.0], + normal: [x, y, z], }); } } diff --git a/solar_engine/src/globals.rs b/solar_engine/src/globals_manager.rs similarity index 80% rename from solar_engine/src/globals.rs rename to solar_engine/src/globals_manager.rs index ecf66ca..5498579 100644 --- a/solar_engine/src/globals.rs +++ b/solar_engine/src/globals_manager.rs @@ -8,25 +8,25 @@ use wgpu::util::DeviceExt; pub struct GlobalsUniform { pub view_proj: [[f32; 4]; 4], pub resolution: [f32; 2], - _padding: [f32; 2], + _pad: [u32; 2], } pub struct GlobalsManager { - buffer: Buffer, - pub(crate) bind_group: BindGroup, - layout: BindGroupLayout, - resolution: [f32; 2], + pub buffer: Buffer, + pub bind_group: BindGroup, + pub layout: BindGroupLayout, + pub resolution: [f32; 2], } impl GlobalsManager { pub fn new(device: &Device, width: u32, height: u32, camera: &Camera) -> Self { let resolution = [width as f32, height as f32]; - let view_proj = camera.build_view_projection_matrix(); + let view_proj = camera.build_view_projection_matrix(); let data = GlobalsUniform { view_proj: view_proj.into(), resolution, - _padding: [0.0; 2], + _pad: [0; 2], }; let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { @@ -66,29 +66,18 @@ impl GlobalsManager { } } - pub fn update(&mut self, queue: &Queue, camera: &Camera) { + pub fn update(&self, queue: &Queue, camera: &Camera) { let view_proj = camera.build_view_projection_matrix(); let data = GlobalsUniform { view_proj: view_proj.into(), resolution: self.resolution, - _padding: [0.0; 2], + _pad: [0; 2], }; queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[data])); } - pub fn resize(&mut self, width: u32, height: u32) { + pub fn resize(&mut self, queue: &Queue, width: u32, height: u32, camera: &Camera) { self.resolution = [width as f32, height as f32]; - } - - pub fn layout(&self) -> &BindGroupLayout { - &self.layout - } - - pub fn bind_group(&self) -> &BindGroup { - &self.bind_group - } - - pub fn resolution(&self) -> [f32; 2] { - self.resolution + self.update(queue, camera); } } diff --git a/solar_engine/src/instance_manager.rs b/solar_engine/src/instance_manager.rs index 2e5b850..e7072e1 100644 --- a/solar_engine/src/instance_manager.rs +++ b/solar_engine/src/instance_manager.rs @@ -1,12 +1,63 @@ +use std::collections::HashMap; +use std::mem::size_of; use wgpu::{Buffer, Device, Queue}; use wgpu::util::DeviceExt; -use std::mem::size_of; use crate::geometry_manager::Shape; -use crate::renderer::{InstanceRaw, RenderInstance}; + +pub struct SubInstance { + pub shape: Shape, + pub material_id: u32, +} + +pub struct RenderInstance { + pub position: cgmath::Vector3, + pub rotation: cgmath::Quaternion, + pub scale: f32, + pub submeshes: Vec, +} + +impl RenderInstance { + pub fn to_raws(&self) -> Vec { + let model = cgmath::Matrix4::from_translation(self.position) + * cgmath::Matrix4::from(self.rotation) + * cgmath::Matrix4::from_scale(self.scale); + + self.submeshes + .iter() + .map(|sub| InstanceRaw { + model: model.into(), + material_id: sub.material_id, + }) + .collect() + } +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct InstanceRaw { + pub model: [[f32; 4]; 4], + pub material_id: u32, +} + +impl InstanceRaw { + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ + wgpu::VertexAttribute { offset: 0, shader_location: 5, format: wgpu::VertexFormat::Float32x4 }, + wgpu::VertexAttribute { offset: 16, shader_location: 6, format: wgpu::VertexFormat::Float32x4 }, + wgpu::VertexAttribute { offset: 32, shader_location: 7, format: wgpu::VertexFormat::Float32x4 }, + wgpu::VertexAttribute { offset: 48, shader_location: 8, format: wgpu::VertexFormat::Float32x4 }, + wgpu::VertexAttribute { offset: 64, shader_location: 9, format: wgpu::VertexFormat::Uint32 }, + ], + } + } +} pub struct InstanceManager { instances: Vec, - raw: Vec, + raw_by_shape: HashMap>, buffer: Buffer, } @@ -21,35 +72,50 @@ impl InstanceManager { Self { instances: Vec::new(), - raw: Vec::new(), + raw_by_shape: HashMap::new(), buffer, } } pub fn set_instances(&mut self, device: &Device, queue: &Queue, instances: Vec) { - self.raw = instances.iter().map(RenderInstance::to_raw).collect(); - let byte_len = (self.raw.len() * size_of::()) as wgpu::BufferAddress; + let mut raw_by_shape: HashMap> = HashMap::new(); + + for instance in &instances { + let model = cgmath::Matrix4::from_translation(instance.position) + * cgmath::Matrix4::from(instance.rotation) + * cgmath::Matrix4::from_scale(instance.scale); + + for sub in &instance.submeshes { + raw_by_shape + .entry(sub.shape) + .or_default() + .push(InstanceRaw { + model: model.into(), + material_id: sub.material_id, + }); + } + } + + let all_raws: Vec = + raw_by_shape.values().flat_map(|v| v.iter().copied()).collect(); + let byte_len = (all_raws.len() * size_of::()) as wgpu::BufferAddress; if byte_len > self.buffer.size() { self.buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Instance Buffer (resized)"), - contents: bytemuck::cast_slice(&self.raw), + contents: bytemuck::cast_slice(&all_raws), usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, }); } else { - queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&self.raw)); + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&all_raws)); } self.instances = instances; + self.raw_by_shape = raw_by_shape; } - pub fn raw_instances_for_shape(&self, shape: Shape) -> Vec<&InstanceRaw> { - self.instances - .iter() - .zip(self.raw.iter()) - .filter(|(inst, _)| inst.shape == shape) - .map(|(_, raw)| raw) - .collect() + pub fn raw_instances_for_shape(&self, shape: Shape) -> &[InstanceRaw] { + self.raw_by_shape.get(&shape).map_or(&[], |v| v.as_slice()) } pub fn buffer(&self) -> &Buffer { diff --git a/solar_engine/src/lib.rs b/solar_engine/src/lib.rs index fd742d7..ec5df5c 100644 --- a/solar_engine/src/lib.rs +++ b/solar_engine/src/lib.rs @@ -1,15 +1,15 @@ mod body; mod simulator; -mod state; +mod engine_state; mod application; mod input; mod camera; -mod light; -mod renderer; +mod light_manager; +mod render_manager; mod instance_manager; -mod globals; +mod globals_manager; mod geometry_manager; -mod material; +mod material_manager; pub use body::Body; @@ -18,12 +18,15 @@ pub use simulator::distance_squared; pub use application::StateApplication as Application; -pub use state::State; +pub use engine_state::EngineState; -pub use renderer::RenderInstance; +pub use instance_manager::RenderInstance; +pub use instance_manager::SubInstance; -pub use light::Light; -pub use light::LightType; +pub use material_manager::GpuMaterial; + +pub use light_manager::Light; +pub use light_manager::LightType; pub use geometry_manager::Shape; diff --git a/solar_engine/src/light.rs b/solar_engine/src/light_manager.rs similarity index 94% rename from solar_engine/src/light.rs rename to solar_engine/src/light_manager.rs index d1e1d75..0093d0c 100644 --- a/solar_engine/src/light.rs +++ b/solar_engine/src/light_manager.rs @@ -73,8 +73,8 @@ impl Light { pub struct LightManager { pub lights: Vec, pub buffer: wgpu::Buffer, - pub bind_group: wgpu::BindGroup, pub count_buffer: wgpu::Buffer, + pub bind_group: wgpu::BindGroup, pub layout: wgpu::BindGroupLayout, pub cluster_buffers: Option, } @@ -183,12 +183,10 @@ impl LightManager { queue.write_buffer(&self.count_buffer, 0, bytemuck::bytes_of(&count)); } - pub fn bind_group(&self) -> &wgpu::BindGroup { - &self.bind_group - } - - pub fn layout(&self) -> &wgpu::BindGroupLayout { - &self.layout + pub fn ensure_cluster_buffers(&mut self, device: &wgpu::Device, assignment: &ClusterAssignment) { + if self.cluster_buffers.is_none() { + self.cluster_buffers = Some(self.create_cluster_buffers(device, assignment)); + } } pub fn create_cluster_buffers( @@ -357,4 +355,18 @@ impl LightManager { cluster_offsets, } } + + pub fn update_all( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + view_matrix: Matrix4, + projection_matrix: Matrix4, + screen_width: f32, + screen_height: f32, + ) { + self.update_gpu(queue); + let assignment = self.compute_cluster_assignments(view_matrix, projection_matrix, screen_width, screen_height); + self.update_cluster_buffers(device, queue, &assignment); + } } diff --git a/solar_engine/src/material.rs b/solar_engine/src/material.rs deleted file mode 100644 index 6150ca0..0000000 --- a/solar_engine/src/material.rs +++ /dev/null @@ -1,58 +0,0 @@ -use wgpu::{Buffer, Device, Queue}; -use wgpu::util::DeviceExt; -use bytemuck::{Pod, Zeroable}; - -#[repr(C)] -#[derive(Copy, Clone, Pod, Zeroable)] -pub struct GpuMaterial { - pub albedo: [f32; 3], - pub emissive: [f32; 3], - pub metallic: f32, - pub roughness: f32, -} - -pub struct MaterialManager { - materials: Vec, - buffer: Buffer, - pub layout: wgpu::BindGroupLayout, - pub bind_group: wgpu::BindGroup, -} - -impl MaterialManager { - pub fn new(device: &Device, materials: Vec) -> Self { - let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Material Buffer"), - contents: bytemuck::cast_slice(&materials), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); - - let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Material BindGroupLayout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("Material BindGroup"), - layout: &layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }); - - Self { materials, buffer, layout, bind_group } - } - - pub fn update(&mut self, queue: &Queue) { - queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&self.materials)); - } -} \ No newline at end of file diff --git a/solar_engine/src/material_manager.rs b/solar_engine/src/material_manager.rs new file mode 100644 index 0000000..67aae9a --- /dev/null +++ b/solar_engine/src/material_manager.rs @@ -0,0 +1,110 @@ +use wgpu::{Buffer, Device, Queue}; +use wgpu::util::DeviceExt; + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct GpuMaterial { + pub base_color: [f32; 4], + pub emission: [f32; 3], + _pad: f32, +} + +impl GpuMaterial { + pub fn new(base_color: [f32; 4], emission: [f32; 3]) -> Self { + Self { + base_color, + emission, + _pad: 0.0, + } + } +} + +pub struct MaterialManager { + materials: Vec, + buffer: Buffer, + pub layout: wgpu::BindGroupLayout, + pub bind_group: wgpu::BindGroup, +} + +impl MaterialManager { + pub fn new(device: &Device) -> Self { + let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Material BindGroup Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let default_materials = vec![ + GpuMaterial { + base_color: [1.0, 1.0, 1.0, 1.0], + emission: [0.0, 0.0, 0.0], + _pad: 0.0, + }, + ]; + + let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Material Buffer"), + contents: bytemuck::cast_slice(&default_materials), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: Some("Material BindGroup"), + }); + + Self { + materials: default_materials, + buffer, + bind_group, + layout, + } + } + + pub fn update(&self, queue: &Queue) { + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&self.materials)); + } + + pub fn add_material(&mut self, material: GpuMaterial) -> u32 { + self.materials.push(material); + + (self.materials.len() - 1) as u32 + } + + pub fn set_materials(&mut self, device: &Device, materials: Vec) { + self.materials = materials; + + self.buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Material Buffer"), + contents: bytemuck::cast_slice(&self.materials), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: self.buffer.as_entire_binding(), + }], + label: Some("Material BindGroup"), + }); + } + + pub fn get_materials(&self) -> &[GpuMaterial] { + &self.materials + } +} \ No newline at end of file diff --git a/solar_engine/src/renderer.rs b/solar_engine/src/render_manager.rs similarity index 54% rename from solar_engine/src/renderer.rs rename to solar_engine/src/render_manager.rs index eac8346..54f73d4 100644 --- a/solar_engine/src/renderer.rs +++ b/solar_engine/src/render_manager.rs @@ -1,149 +1,93 @@ +use wgpu::{Device, Queue, SurfaceConfiguration, SurfaceTexture, TextureView}; use crate::camera::Camera; -use crate::geometry_manager::{GeometryManager, Shape}; -use crate::globals::GlobalsManager; -use crate::instance_manager::InstanceManager; -use crate::light::LightManager; -use wgpu::{Device, Queue, SurfaceTexture, TextureView}; -use crate::material::MaterialManager; - -pub struct RenderInstance { - pub position: cgmath::Vector3, - pub rotation: cgmath::Quaternion, - pub color: [f32; 3], - pub scale: f32, - pub shape: Shape, - pub always_lit: bool, - pub is_transparent: bool -} - -impl RenderInstance { - pub fn to_raw(&self) -> InstanceRaw { - let model = cgmath::Matrix4::from_translation(self.position) - * cgmath::Matrix4::from(self.rotation) - * cgmath::Matrix4::from_scale(self.scale); - InstanceRaw { - model: model.into(), - color: self.color, - flags: (self.always_lit as u32) | ((self.is_transparent as u32) << 1) - } - } -} - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -pub struct InstanceRaw { - model: [[f32; 4]; 4], - color: [f32; 3], - flags: u32, -} - -impl InstanceRaw { - pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> { - wgpu::VertexBufferLayout { - array_stride: size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &[ - wgpu::VertexAttribute { offset: 0, shader_location: 5, format: wgpu::VertexFormat::Float32x4 }, - wgpu::VertexAttribute { offset: 16, shader_location: 6, format: wgpu::VertexFormat::Float32x4 }, - wgpu::VertexAttribute { offset: 32, shader_location: 7, format: wgpu::VertexFormat::Float32x4 }, - wgpu::VertexAttribute { offset: 48, shader_location: 8, format: wgpu::VertexFormat::Float32x4 }, - wgpu::VertexAttribute { offset: 64, shader_location: 9, format: wgpu::VertexFormat::Float32x3 }, - wgpu::VertexAttribute { offset: 76, shader_location: 10, format: wgpu::VertexFormat::Uint32 }, - ], - } - } -} +use crate::geometry_manager::GeometryManager; +use crate::globals_manager::GlobalsManager; +use crate::instance_manager::{InstanceManager, InstanceRaw}; +use crate::light_manager::LightManager; +use crate::material_manager::MaterialManager; #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vertex { - pub(crate) position: [f32; 3], - pub(crate) color: [f32; 3], - pub(crate) normal: [f32; 3], + pub position: [f32; 3], + pub color: [f32; 3], + pub normal: [f32; 3], } impl Vertex { const ATTRIBS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3]; - pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> { + pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { wgpu::VertexBufferLayout { - array_stride: size_of::() as wgpu::BufferAddress, + array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &Self::ATTRIBS, } } } -pub struct Renderer { +pub struct RenderManager { pipeline: wgpu::RenderPipeline, - depth_texture: TextureView, - sample_count: u32 + depth_texture: wgpu::TextureView, + sample_count: u32, } -impl Renderer { +impl RenderManager { pub fn new( device: &Device, - config: &wgpu::SurfaceConfiguration, - global_layout: &wgpu::BindGroupLayout, - light_manager: &mut LightManager, - material_manager: &MaterialManager, + config: &SurfaceConfiguration, + globals: &GlobalsManager, + lights: &LightManager, + materials: &MaterialManager, camera: &Camera, sample_count: u32, ) -> Self { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), + label: Some("Main Shader"), source: wgpu::ShaderSource::Wgsl(include_str!("shaders/shader.wgsl").into()), }); - let cluster_assignment = light_manager.compute_cluster_assignments( + let cluster_assignment = lights.compute_cluster_assignments( camera.build_view_matrix(), camera.build_view_projection_matrix(), config.width as f32, config.height as f32, ); - let cluster_buffers = light_manager.create_cluster_buffers(device, &cluster_assignment); + let cluster_buffers = lights.create_cluster_buffers(device, &cluster_assignment); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), + label: Some("Main Pipeline Layout"), bind_group_layouts: &[ - global_layout, - &light_manager.layout, + &globals.layout, + &lights.layout, &cluster_buffers.layout, - &material_manager.layout, + &materials.layout, ], push_constant_ranges: &[], }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), + label: Some("Main Render Pipeline"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), - buffers: &[Vertex::desc(), InstanceRaw::desc()], compilation_options: Default::default(), + buffers: &[Vertex::desc(), InstanceRaw::desc()], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), + compilation_options: Default::default(), targets: &[Some(wgpu::ColorTargetState { format: config.format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], - compilation_options: Default::default(), }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, + primitive: wgpu::PrimitiveState::default(), depth_stencil: Some(wgpu::DepthStencilState { format: wgpu::TextureFormat::Depth32Float, depth_write_enabled: true, @@ -160,8 +104,7 @@ impl Renderer { cache: None, }); - let depth_texture = - Self::create_depth_texture(device, config.width, config.height, sample_count); + let depth_texture = Self::create_depth_texture(device, config.width, config.height, sample_count); Self { pipeline, @@ -170,25 +113,12 @@ impl Renderer { } } - pub fn resize(&mut self, device: &Device, width: u32, height: u32) { - self.depth_texture = Self::create_depth_texture(device, width, height, self.sample_count); - } - - fn create_depth_texture( - device: &Device, - width: u32, - height: u32, - sample_count: u32, - ) -> TextureView { + fn create_depth_texture(device: &Device, width: u32, height: u32, samples: u32) -> wgpu::TextureView { let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Depth Texture"), - size: wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }, + size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 }, mip_level_count: 1, - sample_count, + sample_count: samples, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, @@ -197,6 +127,10 @@ impl Renderer { texture.create_view(&Default::default()) } + pub fn resize(&mut self, device: &Device, width: u32, height: u32) { + self.depth_texture = Self::create_depth_texture(device, width, height, self.sample_count); + } + pub fn render_frame( &mut self, device: &Device, @@ -204,34 +138,35 @@ impl Renderer { output: SurfaceTexture, view: &TextureView, surface_format: wgpu::TextureFormat, - globals: &mut GlobalsManager, + globals: &GlobalsManager, camera: &Camera, - light_manager: &mut LightManager, + lights: &mut LightManager, geometry: &GeometryManager, instances: &InstanceManager, - material_manager: &mut MaterialManager, + materials: &mut MaterialManager, ) -> Result<(), wgpu::SurfaceError> { - // Update uniform buffer + // Update globals globals.update(queue, camera); - // Update cluster buffers - let assignment = light_manager.compute_cluster_assignments( + // Update light cluster + let assignment = lights.compute_cluster_assignments( camera.build_view_matrix(), camera.build_view_projection_matrix(), - globals.resolution()[0], - globals.resolution()[1], + globals.resolution[0], + globals.resolution[1], ); - light_manager.update_cluster_buffers(device, queue, &assignment); - light_manager.update_gpu(queue); + lights.update_cluster_buffers(device, queue, &assignment); + lights.update_gpu(queue); - // Update material buffer - material_manager.update(queue); - + // Update materials + materials.update(queue); + + // Create MSAA target let multisampled_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Multisample Target"), size: wgpu::Extent3d { - width: globals.resolution()[0] as u32, - height: globals.resolution()[1] as u32, + width: globals.resolution[0] as u32, + height: globals.resolution[1] as u32, depth_or_array_layers: 1, }, mip_level_count: 1, @@ -241,8 +176,10 @@ impl Renderer { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); + let multisampled_view = multisampled_texture.create_view(&Default::default()); + // Begin render let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"), }); @@ -254,12 +191,7 @@ impl Renderer { view: &multisampled_view, resolve_target: Some(view), ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.1, - b: 0.2, - a: 1.0, - }), + load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.05, g: 0.05, b: 0.1, a: 1.0 }), store: wgpu::StoreOp::Store, }, })], @@ -276,24 +208,23 @@ impl Renderer { pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &globals.bind_group, &[]); - pass.set_bind_group(1, &light_manager.bind_group, &[]); - - if let Some(clusters) = &light_manager.cluster_buffers { - pass.set_bind_group(2, &clusters.bind_group, &[]); + pass.set_bind_group(1, &lights.bind_group, &[]); + if let Some(cluster) = &lights.cluster_buffers { + pass.set_bind_group(2, &cluster.bind_group, &[]); } - pass.set_bind_group(3, &material_manager.bind_group, &[]); + pass.set_bind_group(3, &materials.bind_group, &[]); for shape in geometry.shapes() { if let Some(mesh) = geometry.get(&shape) { - let relevant = instances.raw_instances_for_shape(shape); - if relevant.is_empty() { + let instances_for_shape = instances.raw_instances_for_shape(shape); + if instances_for_shape.is_empty() { continue; } pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); pass.set_vertex_buffer(1, instances.buffer().slice(..)); pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint16); - pass.draw_indexed(0..mesh.index_count, 0, 0..relevant.len() as u32); + pass.draw_indexed(0..mesh.index_count, 0, 0..instances_for_shape.len() as u32); } } } diff --git a/solar_engine/src/shaders/shader.wgsl b/solar_engine/src/shaders/shader.wgsl index eb2c9ae..ae749bb 100644 --- a/solar_engine/src/shaders/shader.wgsl +++ b/solar_engine/src/shaders/shader.wgsl @@ -16,25 +16,16 @@ struct InstanceInput { @location(6) model_row1: vec4, @location(7) model_row2: vec4, @location(8) model_row3: vec4, - @location(9) color: vec3, - @location(10) flags: u32, + @location(9) material_id: u32, }; struct VSOutput { @builtin(position) position: vec4, - @location(0) frag_color: vec3, - @location(1) world_pos: vec3, - @location(2) normal: vec3, - @location(3) flags: u32, + @location(0) world_pos: vec3, + @location(1) normal: vec3, + @location(2) material_id: u32, }; -struct LightCount { - count: u32, -}; - -@group(1) @binding(1) -var light_count: LightCount; - struct Globals { view_proj: mat4x4, resolution: vec2, @@ -57,17 +48,18 @@ struct GpuLight { @group(1) @binding(0) var all_lights: array; +@group(1) @binding(1) +var light_count: u32; + @group(2) @binding(0) var cluster_light_indices: array; + @group(2) @binding(1) var cluster_offsets: array>; struct GpuMaterial { - albedo: vec3, - emissive: vec3, - metallic: f32, - roughness: f32, - _pad: vec2, + base_color: vec4, + emission: vec3 }; @group(3) @binding(0) @@ -84,18 +76,17 @@ fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput { instance.model_row3 ); - let world_position = (model * vec4(vertex.position, 1.0)).xyz; + let world_pos = (model * vec4(vertex.position, 1.0)).xyz; let normal_matrix = mat3x3( instance.model_row0.xyz, instance.model_row1.xyz, instance.model_row2.xyz ); - out.position = globals.view_proj * vec4(world_position, 1.0); - out.frag_color = instance.color * vertex.color; - out.world_pos = world_position; + out.position = globals.view_proj * vec4(world_pos, 1.0); + out.world_pos = world_pos; out.normal = normalize(normal_matrix * vertex.normal); - out.flags = instance.flags; + out.material_id = instance.material_id; return out; } @@ -107,8 +98,7 @@ fn compute_cluster_id(frag_coord: vec4, view_pos_z: f32, screen_size: vec2< let x = clamp(u32(x_frac * f32(CLUSTER_COUNT_X)), 0u, CLUSTER_COUNT_X - 1u); let y = clamp(u32(y_frac * f32(CLUSTER_COUNT_Y)), 0u, CLUSTER_COUNT_Y - 1u); - // Z: logarithmic depth - let depth = -view_pos_z; // view-space z is negative + let depth = -view_pos_z; let depth_clamped = clamp(depth, NEAR_PLANE, FAR_PLANE); let log_depth = log2(depth_clamped); let z = clamp(u32((log_depth / log2(FAR_PLANE / NEAR_PLANE)) * f32(CLUSTER_COUNT_Z)), 0u, CLUSTER_COUNT_Z - 1u); @@ -116,18 +106,10 @@ fn compute_cluster_id(frag_coord: vec4, view_pos_z: f32, screen_size: vec2< return x + y * CLUSTER_COUNT_X + z * CLUSTER_COUNT_X * CLUSTER_COUNT_Y; } -fn is_nan_f32(x: f32) -> bool { - return x != x; -} - -fn is_nan_vec3(v: vec3) -> bool { - return any(vec3(v != v)); -} - @fragment fn fs_main(input: VSOutput) -> @location(0) vec4 { - var lighting: vec3 = vec3(0.0); - let always_lit = (input.flags & 0x1u) != 0u; + let material = materials[input.material_id]; + var lighting = vec3(0.0); let cluster_id = compute_cluster_id(input.position, input.world_pos.z, globals.resolution); let offset_info = cluster_offsets[cluster_id]; @@ -137,43 +119,38 @@ fn fs_main(input: VSOutput) -> @location(0) vec4 { for (var i = 0u; i < count; i = i + 1u) { let light_index = cluster_light_indices[offset + i]; let light = all_lights[light_index]; - var light_contrib: vec3 = vec3(0.0); let light_dir = normalize(light.position - input.world_pos); let diff = max(dot(input.normal, light_dir), 0.0); + var light_contrib = vec3(0.0); switch (light.light_type) { - case 0u: { // Directional + case 0u: { light_contrib = light.color * light.intensity * diff; } - case 1u: { // Point + case 1u: { let dist = distance(light.position, input.world_pos); if (dist < light.range) { let attenuation = 1.0 / (dist * dist); light_contrib = light.color * light.intensity * diff * attenuation; } } - case 2u: { // Spot + case 2u: { let spot_dir = normalize(-light.direction); let angle = dot(spot_dir, light_dir); if (angle > light.outer_cutoff) { - let intensity = clamp((angle - light.outer_cutoff) / (light.inner_cutoff - light.outer_cutoff), 0.0, 1.0); + let falloff = clamp((angle - light.outer_cutoff) / (light.inner_cutoff - light.outer_cutoff), 0.0, 1.0); let dist = distance(light.position, input.world_pos); let attenuation = 1.0 / (dist * dist); - light_contrib = light.color * light.intensity * diff * attenuation * intensity; + light_contrib = light.color * light.intensity * diff * attenuation * falloff; } } default: {} } - if (!always_lit) { - lighting += light_contrib; - } + lighting += light_contrib; } - if (always_lit) { - lighting = vec3(1.0, 1.0, 1.0) * 2.0; - } - - return vec4(input.frag_color * lighting, 1.0); + let final_rgb = material.base_color.rgb * lighting + material.emission; + return vec4(final_rgb, material.base_color.a); }