diff --git a/simulator/src/main.rs b/simulator/src/main.rs index 5a45838..10a5719 100644 --- a/simulator/src/main.rs +++ b/simulator/src/main.rs @@ -49,9 +49,7 @@ pub async fn run() { let sim = simulator_clone.read().unwrap(); let bodies = &sim.bodies; - let sun_pos = (bodies[0].position / 1.496e11); - - // let light_offset = Vector3::new(0.0, 0.0, 0.1); + let sun_pos = bodies[0].position / 1.496e11; state.light_manager.clear(); state.light_manager.add_light(Light { @@ -59,9 +57,9 @@ pub async fn run() { direction: Vector3::new(0.0, 0.0, 0.0), color: Vector3::from([1.0, 1.0, 0.8]), intensity: 5.0, - range: 0.0, + range: 1.0, inner_cutoff: 0.0, - light_type: LightType::Directional, + light_type: LightType::Point, outer_cutoff: 0.0, }); diff --git a/solar_engine/src/light.rs b/solar_engine/src/light.rs index 7c85124..16762e7 100644 --- a/solar_engine/src/light.rs +++ b/solar_engine/src/light.rs @@ -52,7 +52,14 @@ pub struct LightManager { pub lights: Vec, pub buffer: wgpu::Buffer, pub bind_group: wgpu::BindGroup, - pub layout: wgpu::BindGroupLayout, + pub count_buffer: wgpu::Buffer, + pub layout: wgpu::BindGroupLayout +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable, Debug)] +pub struct LightCount { + pub count: u32, } impl LightManager { @@ -60,31 +67,58 @@ impl LightManager { 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, + usage: wgpu::BufferUsages::STORAGE | 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, + entries: &[ + // Binding 0: Storage buffer for lights + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, }, - count: None, - }], + // Binding 1: Uniform buffer for light count + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let count_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Light Count Buffer"), + size: size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, }); 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(), - }], + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: count_buffer.as_entire_binding(), + }, + ], }); Self { @@ -92,6 +126,7 @@ impl LightManager { buffer, bind_group, layout, + count_buffer, } } @@ -102,6 +137,11 @@ impl LightManager { 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)); + + let count = LightCount { + count: self.lights.len() as u32, + }; + queue.write_buffer(&self.count_buffer, 0, bytemuck::bytes_of(&count)); } pub fn bind_group(&self) -> &wgpu::BindGroup { diff --git a/solar_engine/src/shader.wgsl b/solar_engine/src/shader.wgsl index 2368ee3..0da34f3 100644 --- a/solar_engine/src/shader.wgsl +++ b/solar_engine/src/shader.wgsl @@ -21,6 +21,13 @@ struct VSOutput { @location(3) flags: u32, }; +struct LightCount { + count: u32, +}; + +@group(1) @binding(1) +var light_count: LightCount; + struct Globals { view_proj: mat4x4, } @@ -30,12 +37,17 @@ var globals: Globals; struct GpuLight { position: vec3, + light_type: u32, color: vec3, intensity: f32, + direction: vec3, + range: f32, + inner_cutoff: f32, + outer_cutoff: f32, }; @group(1) @binding(0) -var lights: array; +var all_lights: array; @vertex fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput { @@ -66,22 +78,46 @@ fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput { @fragment fn fs_main(input: VSOutput) -> @location(0) vec4 { - var lighting: vec3; - + var lighting: vec3 = vec3(0.0); let always_lit = (input.flags & 0x1u) != 0u; if (always_lit) { return vec4(input.frag_color * 2.0, 1.0); - } else { - lighting = 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); + for (var i = 0u; i < light_count.count; i = i + 1u) { + let light = all_lights[i]; + 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); + + switch light.light_type { + case 0u: { // Directional + light_contrib = light.color * light.intensity * diff; + } + case 1u: { // Point + 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 + 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 dist = distance(light.position, input.world_pos); + let attenuation = 1.0 / (dist * dist); + light_contrib = light.color * light.intensity * diff * attenuation * intensity; + } + } + default: {} + } + + lighting += light_contrib; + } + + return vec4(input.frag_color * lighting, 1.0); }