Added directional light from sun

This commit is contained in:
Verox001 2025-05-06 17:12:22 +02:00
parent 6c0cfbefce
commit 5a444c3e07
6 changed files with 220 additions and 30 deletions

View File

@ -1,5 +1,5 @@
use cgmath::Rotation3; use cgmath::{Rotation3, Vector3};
use solar_engine::{Application, Body, Key, KeyState, Simulator}; use solar_engine::{Application, Body, Key, KeyState, Light, LightType, Simulator};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::thread; use std::thread;
@ -47,14 +47,34 @@ pub async fn run() {
let sim = simulator_clone.read().unwrap(); let sim = simulator_clone.read().unwrap();
let bodies = &sim.bodies; 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 let instances = bodies
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, b)| { .map(|(i, b)| {
solar_engine::RenderInstance { solar_engine::RenderInstance {
position: cgmath::Vector3::new( position: Vector3::new(
(b.position[0] / 1.496e11) as f32, ((b.position[0] / 1.496e11) as f32) - sun_pos.x,
(b.position[1] / 1.496e11) as f32, ((b.position[1] / 1.496e11) as f32) - sun_pos.y,
0.0, 0.0,
), ),
rotation: cgmath::Quaternion::from_angle_z(cgmath::Deg(0.0)), rotation: cgmath::Quaternion::from_angle_z(cgmath::Deg(0.0)),

View File

@ -5,6 +5,7 @@ mod render;
mod application; mod application;
mod input; mod input;
mod camera; mod camera;
mod light;
pub use body::Body; pub use body::Body;
@ -21,4 +22,7 @@ pub use state::State;
pub use input::Key; pub use input::Key;
pub use input::map_winit_key; pub use input::map_winit_key;
pub use input::InputEvent; pub use input::InputEvent;
pub use input::KeyState; pub use input::KeyState;
pub use light::Light;
pub use light::LightType;

118
solar_engine/src/light.rs Normal file
View File

@ -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<f32>,
pub direction: Vector3<f32>,
pub color: Vector3<f32>,
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<Light>,
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::<GpuLight>()) 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<GpuLight> = 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();
}
}

View File

@ -6,7 +6,6 @@ pub struct Globals {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Shape { pub enum Shape {
Polygon,
Circle, Circle,
Sphere, Sphere,
} }
@ -74,11 +73,12 @@ impl SampleCount {
pub struct Vertex { pub struct Vertex {
pub(crate) position: [f32; 3], pub(crate) position: [f32; 3],
pub(crate) color: [f32; 3], pub(crate) color: [f32; 3],
pub(crate) normal: [f32; 3],
} }
impl Vertex { impl Vertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] = const ATTRIBS: [wgpu::VertexAttribute; 3] =
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3]; wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3];
pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> { pub(crate) fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout { wgpu::VertexBufferLayout {
@ -90,14 +90,22 @@ impl Vertex {
} }
pub fn create_circle_vertices(segment_count: usize, radius: f32, color: [f32; 3]) -> (Vec<Vertex>, Vec<u16>) { pub fn create_circle_vertices(segment_count: usize, radius: f32, color: [f32; 3]) -> (Vec<Vertex>, Vec<u16>) {
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![]; let mut indices = vec![];
for i in 0..=segment_count { for i in 0..=segment_count {
let theta = (i as f32) / (segment_count as f32) * std::f32::consts::TAU; let theta = (i as f32) / (segment_count as f32) * std::f32::consts::TAU;
let x = radius * theta.cos(); let x = radius * theta.cos();
let y = radius * theta.sin(); 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 { 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 x = r * theta.cos();
let z = r * theta.sin(); let z = r * theta.sin();
let normal = [x, y, z];
vertices.push(Vertex { vertices.push(Vertex {
position: [x * radius, y * radius, z * radius], position: [x * radius, y * radius, z * radius],
color, color,
normal,
}); });
} }
} }

View File

@ -1,6 +1,7 @@
struct VertexInput { struct VertexInput {
@location(0) position: vec3<f32>, @location(0) position: vec3<f32>,
@location(1) color: vec3<f32>, @location(1) color: vec3<f32>,
@location(2) normal: vec3<f32>,
}; };
struct InstanceInput { struct InstanceInput {
@ -13,7 +14,9 @@ struct InstanceInput {
struct VSOutput { struct VSOutput {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) color: vec3<f32>, @location(0) frag_color: vec3<f32>,
@location(1) world_pos: vec3<f32>,
@location(2) normal: vec3<f32>,
}; };
struct Globals { struct Globals {
@ -23,21 +26,52 @@ struct Globals {
@group(0) @binding(0) @group(0) @binding(0)
var<uniform> globals: Globals; var<uniform> globals: Globals;
struct GpuLight {
position: vec3<f32>,
color: vec3<f32>,
intensity: f32,
};
@group(1) @binding(0)
var<uniform> lights: array<GpuLight, 10>;
@vertex @vertex
fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput { fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput {
var out: VSOutput; var out: VSOutput;
let model = mat4x4<f32>( let model = mat4x4<f32>(
instance.model_row0, instance.model_row0,
instance.model_row1, instance.model_row1,
instance.model_row2, instance.model_row2,
instance.model_row3 instance.model_row3
); );
out.position = globals.view_proj * model * vec4<f32>(vertex.position, 1.0);
out.color = instance.color; let world_position = (model * vec4<f32>(vertex.position, 1.0)).xyz;
let normal_matrix = mat3x3<f32>(
instance.model_row0.xyz,
instance.model_row1.xyz,
instance.model_row2.xyz
);
out.position = globals.view_proj * vec4<f32>(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; return out;
} }
@fragment @fragment
fn fs_main(input: VSOutput) -> @location(0) vec4<f32> { fn fs_main(input: VSOutput) -> @location(0) vec4<f32> {
return vec4<f32>(input.color, 1.0); var lighting: vec3<f32> = vec3<f32>(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<f32>(final_color, 1.0);
} }

View File

@ -9,6 +9,7 @@ use wgpu::{Adapter, Device, Instance, PresentMode, Queue, Surface, SurfaceCapabi
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use winit::window::{Window}; use winit::window::{Window};
use crate::camera::Camera; 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}; use crate::render::{create_circle_vertices, create_sphere_vertices, Geometry, Globals, InstanceRaw, RenderInstance, SampleCount, Shape, Vertex};
pub struct State<'a> { pub struct State<'a> {
@ -32,6 +33,8 @@ pub struct State<'a> {
camera: Camera, camera: Camera,
depth_texture: wgpu::TextureView, depth_texture: wgpu::TextureView,
pub light_manager: LightManager,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
@ -83,7 +86,9 @@ impl<'a> State<'a> {
label: Some("Global Bind Group"), 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 geometries = Self::create_geometries(&device);
let instances = vec![]; let instances = vec![];
@ -112,9 +117,15 @@ impl<'a> State<'a> {
instance_buffer, instance_buffer,
camera, camera,
depth_texture, depth_texture,
light_manager
} }
} }
fn update_lights(&mut self) {
let light_data: Vec<GpuLight> = 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 { fn create_depth_texture(device: &Device, width: u32, height: u32, sample_count: u32) -> wgpu::TextureView {
let texture = device.create_texture(&wgpu::TextureDescriptor { let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Depth Texture"), label: Some("Depth Texture"),
@ -136,18 +147,6 @@ impl<'a> State<'a> {
fn create_geometries(device: &Device) -> HashMap<Shape, Geometry> { fn create_geometries(device: &Device) -> HashMap<Shape, Geometry> {
let mut geometries = HashMap::new(); 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_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); let circle_geometry = Self::create_geometry(device, &circle_vertices, &circle_indices);
geometries.insert(Shape::Circle, circle_geometry); 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 { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"), label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
@ -216,7 +215,7 @@ impl<'a> State<'a> {
let render_pipeline_layout = let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"), 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: &[], push_constant_ranges: &[],
}); });
@ -393,6 +392,10 @@ impl<'a> State<'a> {
render_pass.set_pipeline(&self.render_pipeline); render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.global_bind_group, &[]); 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() { for shape in self.geometries.keys().copied() {
let geometry = &self.geometries[&shape]; let geometry = &self.geometries[&shape];