From 5a444c3e0723ca3b7e01594923a262902190e097 Mon Sep 17 00:00:00 2001 From: Verox001 Date: Tue, 6 May 2025 17:12:22 +0200 Subject: [PATCH] Added directional light from sun --- simulator/src/main.rs | 30 +++++++-- solar_engine/src/lib.rs | 6 +- solar_engine/src/light.rs | 118 +++++++++++++++++++++++++++++++++++ solar_engine/src/render.rs | 21 +++++-- solar_engine/src/shader.wgsl | 42 +++++++++++-- solar_engine/src/state.rs | 33 +++++----- 6 files changed, 220 insertions(+), 30 deletions(-) create mode 100644 solar_engine/src/light.rs diff --git a/simulator/src/main.rs b/simulator/src/main.rs index e64d243..c5e3e22 100644 --- a/simulator/src/main.rs +++ b/simulator/src/main.rs @@ -1,5 +1,5 @@ -use cgmath::Rotation3; -use solar_engine::{Application, Body, Key, KeyState, Simulator}; +use cgmath::{Rotation3, Vector3}; +use solar_engine::{Application, Body, Key, KeyState, Light, LightType, Simulator}; use std::sync::{Arc, RwLock}; use std::thread; @@ -47,14 +47,34 @@ pub async fn run() { let sim = simulator_clone.read().unwrap(); let bodies = &sim.bodies; + let sun_pos = Vector3::new( + (bodies[0].position[0] / 1.496e11) as f32, + (bodies[0].position[1] / 1.496e11) as f32, + 0.0, + ); + + let light_offset = Vector3::new(0.0, 0.0, 0.1); + + state.light_manager.clear(); + state.light_manager.add_light(Light { + position: sun_pos + light_offset, + direction: Vector3::new(0.0, 0.0, 0.0), + color: Vector3::from([1.0, 1.0, 0.8]), + intensity: 5.0, + range: 0.0, + inner_cutoff: 0.0, + light_type: LightType::Directional, + outer_cutoff: 0.0, + }); + let instances = bodies .iter() .enumerate() .map(|(i, b)| { solar_engine::RenderInstance { - position: cgmath::Vector3::new( - (b.position[0] / 1.496e11) as f32, - (b.position[1] / 1.496e11) as f32, + position: Vector3::new( + ((b.position[0] / 1.496e11) as f32) - sun_pos.x, + ((b.position[1] / 1.496e11) as f32) - sun_pos.y, 0.0, ), rotation: cgmath::Quaternion::from_angle_z(cgmath::Deg(0.0)), diff --git a/solar_engine/src/lib.rs b/solar_engine/src/lib.rs index 0a8b8a8..660fddc 100644 --- a/solar_engine/src/lib.rs +++ b/solar_engine/src/lib.rs @@ -5,6 +5,7 @@ mod render; mod application; mod input; mod camera; +mod light; pub use body::Body; @@ -21,4 +22,7 @@ pub use state::State; pub use input::Key; pub use input::map_winit_key; pub use input::InputEvent; -pub use input::KeyState; \ No newline at end of file +pub use input::KeyState; + +pub use light::Light; +pub use light::LightType; \ No newline at end of file diff --git a/solar_engine/src/light.rs b/solar_engine/src/light.rs new file mode 100644 index 0000000..7c85124 --- /dev/null +++ b/solar_engine/src/light.rs @@ -0,0 +1,118 @@ +use bytemuck::{Pod, Zeroable}; +use cgmath::Vector3; + +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum LightType { + Directional = 0, + Point = 1, + Spot = 2 +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable, Debug)] +pub struct GpuLight { + pub position: [f32; 3], + pub light_type: u32, + pub color: [f32; 3], + pub intensity: f32, + pub direction: [f32; 3], + pub range: f32, + pub inner_cutoff: f32, + pub outer_cutoff: f32, +} + +pub struct Light { + pub light_type: LightType, + pub position: Vector3, + pub direction: Vector3, + pub color: Vector3, + pub intensity: f32, + pub range: f32, + pub inner_cutoff: f32, + pub outer_cutoff: f32, +} + +impl Light { + pub fn to_gpu(&self) -> GpuLight { + GpuLight { + position: self.position.into(), + light_type: self.light_type as u32, + color: self.color.into(), + intensity: self.intensity, + direction: self.direction.into(), + range: self.range, + inner_cutoff: self.inner_cutoff, + outer_cutoff: self.outer_cutoff, + } + } +} + +pub struct LightManager { + pub lights: Vec, + pub buffer: wgpu::Buffer, + pub bind_group: wgpu::BindGroup, + pub layout: wgpu::BindGroupLayout, +} + +impl LightManager { + pub fn new(device: &wgpu::Device, max_lights: usize) -> Self { + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Light Buffer"), + size: (max_lights * size_of::()) as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Light Bind Group Layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Light Bind Group"), + layout: &layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + Self { + lights: Vec::new(), + buffer, + bind_group, + layout, + } + } + + pub fn add_light(&mut self, light: Light) { + self.lights.push(light); + } + + pub fn update_gpu(&self, queue: &wgpu::Queue) { + let data: Vec = self.lights.iter().map(|l| l.to_gpu()).collect(); + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&data)); + } + + pub fn bind_group(&self) -> &wgpu::BindGroup { + &self.bind_group + } + + pub fn layout(&self) -> &wgpu::BindGroupLayout { + &self.layout + } + + pub fn clear(&mut self) { + self.lights.clear(); + } +} \ No newline at end of file diff --git a/solar_engine/src/render.rs b/solar_engine/src/render.rs index f812328..5b23370 100644 --- a/solar_engine/src/render.rs +++ b/solar_engine/src/render.rs @@ -6,7 +6,6 @@ pub struct Globals { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Shape { - Polygon, Circle, Sphere, } @@ -74,11 +73,12 @@ impl SampleCount { pub struct Vertex { pub(crate) position: [f32; 3], pub(crate) color: [f32; 3], + pub(crate) normal: [f32; 3], } impl Vertex { - const ATTRIBS: [wgpu::VertexAttribute; 2] = - wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3]; + const ATTRIBS: [wgpu::VertexAttribute; 3] = + wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3]; pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> { wgpu::VertexBufferLayout { @@ -90,14 +90,22 @@ impl Vertex { } pub fn create_circle_vertices(segment_count: usize, radius: f32, color: [f32; 3]) -> (Vec, Vec) { - let mut vertices = vec![Vertex { position: [0.0, 0.0, 0.0], color }]; + let mut vertices = vec![Vertex { + position: [0.0, 0.0, 0.0], + color, + normal: [0.0, 0.0, 1.0], + }]; let mut indices = vec![]; for i in 0..=segment_count { let theta = (i as f32) / (segment_count as f32) * std::f32::consts::TAU; let x = radius * theta.cos(); let y = radius * theta.sin(); - vertices.push(Vertex { position: [x, y, 0.0], color }); + vertices.push(Vertex { + position: [x, y, 0.0], + color, + normal: [0.0, 0.0, 1.0], + }); } for i in 1..=segment_count { @@ -123,9 +131,12 @@ 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, }); } } diff --git a/solar_engine/src/shader.wgsl b/solar_engine/src/shader.wgsl index 324f6da..4bd2e89 100644 --- a/solar_engine/src/shader.wgsl +++ b/solar_engine/src/shader.wgsl @@ -1,6 +1,7 @@ struct VertexInput { @location(0) position: vec3, @location(1) color: vec3, + @location(2) normal: vec3, }; struct InstanceInput { @@ -13,7 +14,9 @@ struct InstanceInput { struct VSOutput { @builtin(position) position: vec4, - @location(0) color: vec3, + @location(0) frag_color: vec3, + @location(1) world_pos: vec3, + @location(2) normal: vec3, }; struct Globals { @@ -23,21 +26,52 @@ struct Globals { @group(0) @binding(0) var globals: Globals; +struct GpuLight { + position: vec3, + color: vec3, + intensity: f32, +}; + +@group(1) @binding(0) +var lights: array; + @vertex fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput { var out: VSOutput; + let model = mat4x4( instance.model_row0, instance.model_row1, instance.model_row2, instance.model_row3 ); - out.position = globals.view_proj * model * vec4(vertex.position, 1.0); - out.color = instance.color; + + let world_position = (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.normal = normalize(normal_matrix * vertex.normal); + return out; } @fragment fn fs_main(input: VSOutput) -> @location(0) vec4 { - return vec4(input.color, 1.0); + var lighting: vec3 = vec3(0.0); + + for (var i = 0u; i < 10u; i = i + 1u) { + let light = lights[i]; + let light_dir = normalize(light.position - input.world_pos); + let diff = max(dot(input.normal, light_dir), 0.0); + lighting += light.color * light.intensity * diff; + } + + let final_color = input.frag_color * lighting; + return vec4(final_color, 1.0); } diff --git a/solar_engine/src/state.rs b/solar_engine/src/state.rs index 6d25f1d..662f6e1 100644 --- a/solar_engine/src/state.rs +++ b/solar_engine/src/state.rs @@ -9,6 +9,7 @@ use wgpu::{Adapter, Device, Instance, PresentMode, Queue, Surface, SurfaceCapabi use winit::dpi::PhysicalSize; use winit::window::{Window}; use crate::camera::Camera; +use crate::light::{GpuLight, LightManager}; use crate::render::{create_circle_vertices, create_sphere_vertices, Geometry, Globals, InstanceRaw, RenderInstance, SampleCount, Shape, Vertex}; pub struct State<'a> { @@ -32,6 +33,8 @@ pub struct State<'a> { camera: Camera, depth_texture: wgpu::TextureView, + + pub light_manager: LightManager, } impl<'a> State<'a> { @@ -83,7 +86,9 @@ impl<'a> State<'a> { label: Some("Global Bind Group"), }); - let render_pipeline = Self::create_render_pipeline(&device, &config, sample_count.0, &global_bind_group_layout); + let light_manager = LightManager::new(&device, 10); + + let render_pipeline = Self::create_render_pipeline(&device, &config, sample_count.0, &global_bind_group_layout, &light_manager); let geometries = Self::create_geometries(&device); let instances = vec![]; @@ -112,9 +117,15 @@ impl<'a> State<'a> { instance_buffer, camera, depth_texture, + light_manager } } + fn update_lights(&mut self) { + let light_data: Vec = self.light_manager.lights.iter().map(|l| l.to_gpu()).collect(); + self.queue.write_buffer(&self.light_manager.buffer, 0, bytemuck::cast_slice(&light_data)); + } + fn create_depth_texture(device: &Device, width: u32, height: u32, sample_count: u32) -> wgpu::TextureView { let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Depth Texture"), @@ -136,18 +147,6 @@ impl<'a> State<'a> { fn create_geometries(device: &Device) -> HashMap { let mut geometries = HashMap::new(); - let polygon_vertices = vec![ - Vertex { position: [-0.0868241, 0.49240386, 0.0], color: [0.5, 0.0, 0.5] }, - Vertex { position: [-0.49513406, 0.06958647, 0.0], color: [0.5, 0.0, 0.5] }, - Vertex { position: [-0.21918549, -0.44939706, 0.0], color: [0.5, 0.0, 0.5] }, - Vertex { position: [0.35966998, -0.3473291, 0.0], color: [0.5, 0.0, 0.5] }, - Vertex { position: [0.44147372, 0.2347359, 0.0], color: [0.5, 0.0, 0.5] }, - ]; - let polygon_indices = vec![0, 1, 4, 1, 2, 4, 2, 3, 4]; - - let polygon_geometry = Self::create_geometry(device, &polygon_vertices, &polygon_indices); - geometries.insert(Shape::Polygon, polygon_geometry); - let (circle_vertices, circle_indices) = create_circle_vertices(512, 0.5, [0.5, 0.5, 0.5]); let circle_geometry = Self::create_geometry(device, &circle_vertices, &circle_indices); geometries.insert(Shape::Circle, circle_geometry); @@ -207,7 +206,7 @@ impl<'a> State<'a> { }) } - fn create_render_pipeline(device: &Device, config: &SurfaceConfiguration, sample_count: u32, global_bind_group_layout: &wgpu::BindGroupLayout) -> wgpu::RenderPipeline { + fn create_render_pipeline(device: &Device, config: &SurfaceConfiguration, sample_count: u32, global_bind_group_layout: &wgpu::BindGroupLayout, light_manager: &LightManager) -> wgpu::RenderPipeline { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), @@ -216,7 +215,7 @@ impl<'a> State<'a> { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&global_bind_group_layout], + bind_group_layouts: &[&global_bind_group_layout, &light_manager.layout], push_constant_ranges: &[], }); @@ -393,6 +392,10 @@ impl<'a> State<'a> { render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, &self.global_bind_group, &[]); + + // Update the light manager buffer + self.light_manager.update_gpu(&self.queue); + render_pass.set_bind_group(1, &self.light_manager.bind_group, &[]); for shape in self.geometries.keys().copied() { let geometry = &self.geometries[&shape];