Compare commits

...

2 Commits

Author SHA1 Message Date
SeanOMik f0b413d9ae
render: resize light grid with window, improve light buffer, add spot lights to the light cull compute
Spot lights are buggy. They get culled when they shouldn't be, maybe still an issue with the light grid :(
2024-03-20 11:41:40 -04:00
SeanOMik 65ff7c4f23
render: retrieve light properties from components 2024-03-19 22:40:15 -04:00
9 changed files with 313 additions and 291 deletions

View File

@ -120,21 +120,6 @@ async fn main() {
Transform::from_xyz(0.0, 0.0, 0.0), Transform::from_xyz(0.0, 0.0, 0.0),
)); ));
/* world.spawn((
separate_scene.clone(),
Transform::from_xyz(0.0, -5.0, -10.0),
)); */
/* {
let cube_tran = Transform::from_xyz(-5.9026427, -1.8953488, -10.0);
//cube_tran.rotate_y(math::Angle::Degrees(180.0));
world.spawn((
cube_tran,
crate_mesh.clone(),
CubeFlag,
));
} */
{ {
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0); let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
light_tran.scale = Vec3::new(0.5, 0.5, 0.5); light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
@ -142,36 +127,27 @@ async fn main() {
light_tran.rotate_y(math::Angle::Degrees(25.0)); light_tran.rotate_y(math::Angle::Degrees(25.0));
world.spawn(( world.spawn((
DirectionalLight { DirectionalLight {
color: Vec3::new(1.0, 1.0, 1.0), enabled: false,
ambient: 0.3, color: Vec3::ONE,
diffuse: 1.0, intensity: 0.35
specular: 1.3, //..Default::default()
}, },
light_tran, light_tran,
//cube_mesh.clone(),
)); ));
} }
{ {
//let mut light_tran = Transform::from_xyz(-3.5, 0.2, -4.5);
//light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
world.spawn(( world.spawn((
PointLight { PointLight {
enabled: true,
color: Vec3::new(0.0, 0.0, 1.0), color: Vec3::new(0.0, 0.0, 1.0),
intensity: 1.0,
intensity: 3.3, range: 2.0,
..Default::default()
constant: 1.0,
linear: 0.09,
quadratic: 0.032,
ambient: 0.2,
diffuse: 1.0,
specular: 1.3,
}, },
Transform::new( Transform::new(
//Vec3::new(-5.0, 1.0, -1.28), //Vec3::new(-5.0, 1.0, -1.28),
Vec3::new(-5.0, 1.0, -0.28), Vec3::new(-5.0, 1.0, -0.0),
//Vec3::new(-10.0, 0.94, -0.28), //Vec3::new(-10.0, 0.94, -0.28),
Quat::IDENTITY, Quat::IDENTITY,
@ -179,6 +155,44 @@ async fn main() {
), ),
cube_mesh.clone(), cube_mesh.clone(),
)); ));
world.spawn((
PointLight {
enabled: true,
color: Vec3::new(0.0, 0.5, 1.0),
intensity: 1.0,
range: 1.0,
..Default::default()
},
Transform::new(
Vec3::new(-3.0, 0.2, -1.5),
//Vec3::new(-5.0, 1.0, -0.28),
//Vec3::new(-10.0, 0.94, -0.28),
Quat::IDENTITY,
Vec3::new(0.15, 0.15, 0.15),
),
cube_mesh.clone(),
));
/* world.spawn((
SpotLight {
enabled: true,
color: Vec3::new(1.0, 0.0, 0.0),
intensity: 1.0,
range: 1.5,
..Default::default()
},
Transform::new(
Vec3::new(0.0, 0.2, -1.5),
//Vec3::new(-5.0, 1.0, -0.28),
//Vec3::new(-10.0, 0.94, -0.28),
Quat::IDENTITY,
Vec3::new(0.15, 0.15, 0.15),
),
cube_mesh.clone(),
)); */
} }
/* { /* {

View File

@ -1,11 +1,18 @@
use lyra_ecs::Component; use lyra_ecs::Component;
#[derive(Default, Debug, Clone, Component)] #[derive(Debug, Clone, Component)]
pub struct DirectionalLight { pub struct DirectionalLight {
//pub direction: glam::Quat, pub enabled: bool,
pub color: glam::Vec3, pub color: glam::Vec3,
pub intensity: f32,
pub ambient: f32, }
pub diffuse: f32,
pub specular: f32, impl Default for DirectionalLight {
fn default() -> Self {
Self {
enabled: true,
color: glam::Vec3::new(1.0, 1.0, 1.0),
intensity: 1.0,
}
}
} }

View File

@ -6,7 +6,7 @@ use lyra_ecs::{Entity, Tick, World, query::{Entities, TickOf}};
pub use point::*; pub use point::*;
pub use spotlight::*; pub use spotlight::*;
use std::{collections::{HashMap, VecDeque}, marker::PhantomData}; use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem};
use crate::math::Transform; use crate::math::Transform;
@ -103,13 +103,14 @@ pub(crate) struct LightUniformBuffers {
pub buffer: wgpu::Buffer, pub buffer: wgpu::Buffer,
pub bind_group_pair: BindGroupPair, pub bind_group_pair: BindGroupPair,
pub light_indexes: HashMap<Entity, u32>, pub light_indexes: HashMap<Entity, u32>,
dead_indices: VecDeque<u32>,
pub current_light_idx: u32, pub current_light_idx: u32,
} }
impl LightUniformBuffers { impl LightUniformBuffers {
pub fn new(device: &wgpu::Device) -> Self { pub fn new(device: &wgpu::Device) -> Self {
let limits = device.limits(); let limits = device.limits();
// TODO: check this limit somehow // TODO: ensure we dont write over this limit
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2; let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2;
let buffer = device.create_buffer( let buffer = device.create_buffer(
@ -161,45 +162,88 @@ impl LightUniformBuffers {
bind_group_pair: BindGroupPair::new(bindgroup, bindgroup_layout), bind_group_pair: BindGroupPair::new(bindgroup, bindgroup_layout),
light_indexes: Default::default(), light_indexes: Default::default(),
current_light_idx: 0, current_light_idx: 0,
dead_indices: VecDeque::new(),
} }
} }
/// Returns the index for the entity, and if this index is new
fn get_index_for(&mut self, missed: &mut HashMap<Entity, u32>, entity: Entity) -> (bool, u32) {
let idx = missed.remove(&entity)
.map(|v| (false, v))
.or_else(||
self.dead_indices.pop_front()
.map(|v| (true, v))
)
.unwrap_or_else(|| {
let t = self.current_light_idx;
self.current_light_idx += 1;
(true, t)
});
idx
}
pub fn update_lights(&mut self, queue: &wgpu::Queue, world_tick: Tick, world: &World) { pub fn update_lights(&mut self, queue: &wgpu::Queue, world_tick: Tick, world: &World) {
let mut lights = LightsUniform::default(); // used to detect what lights were removed
let mut missed_lights: HashMap<Entity, u32> = self.light_indexes.drain().collect();
for (entity, point_light, transform, light_epoch, transform_epoch) for (entity, point_light, transform, light_epoch, transform_epoch)
in world.view_iter::<(Entities, &PointLight, &Transform, TickOf<PointLight>, TickOf<Transform>)>() { in world.view_iter::<(Entities, &PointLight, &Transform, TickOf<PointLight>, TickOf<Transform>)>() {
// TODO: dont update light every frame let (new, idx) = self.get_index_for(&mut missed_lights, entity);
let idx = *self.light_indexes.entry(entity) self.light_indexes.insert(entity, idx);
.or_insert_with(|| {
let t = self.current_light_idx;
self.current_light_idx += 1;
t
}) as usize;
let uniform = LightUniform::from_point_light_bundle(&point_light, &transform); if new || light_epoch == world_tick || transform_epoch == world_tick {
lights.data[idx] = uniform; let uniform = LightUniform::from_point_light_bundle(&point_light, &transform);
let offset = mem::size_of::<u32>() * 4 + mem::size_of::<LightUniform>() * idx as usize;
queue.write_buffer(&self.buffer, offset as _, bytemuck::cast_slice(&[uniform]));
}
} }
if let Some((entity, dir_light, transform)) = for (entity, spot_light, transform, light_epoch, transform_epoch)
world.view_iter::<(Entities, &DirectionalLight, &Transform)>().next() { in world.view_iter::<(Entities, &SpotLight, &Transform, TickOf<SpotLight>, TickOf<Transform>)>() {
let idx = *self.light_indexes.entry(entity) let (new, idx) = self.get_index_for(&mut missed_lights, entity);
.or_insert_with(|| { self.light_indexes.insert(entity, idx);
let t = self.current_light_idx;
self.current_light_idx += 1;
t
}) as usize;
let uniform = LightUniform::from_directional_bundle(&dir_light, &transform); if new || light_epoch == world_tick || transform_epoch == world_tick {
lights.data[idx] = uniform; let uniform = LightUniform::from_spot_light_bundle(&spot_light, &transform);
let offset = mem::size_of::<u32>() * 4 + mem::size_of::<LightUniform>() * idx as usize;
queue.write_buffer(&self.buffer, offset as _, bytemuck::cast_slice(&[uniform]));
}
} }
lights.light_count = self.light_indexes.len() as u32; for (entity, dir_light, transform, light_epoch, transform_epoch)
in world.view_iter::<(Entities, &DirectionalLight, &Transform, TickOf<DirectionalLight>, TickOf<Transform>)>() {
// update the light count in the struct let (new, idx) = self.get_index_for(&mut missed_lights, entity);
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights])); self.light_indexes.insert(entity, idx);
if new || light_epoch == world_tick || transform_epoch == world_tick {
let uniform = LightUniform::from_directional_bundle(&dir_light, &transform);
let offset = mem::size_of::<u32>() * 4 + mem::size_of::<LightUniform>() * idx as usize;
queue.write_buffer(&self.buffer, offset as _, bytemuck::cast_slice(&[uniform]));
}
}
// anything left in missed_lights were lights that were deleted
let len = missed_lights.len();
self.dead_indices.reserve(len);
for (_, v) in missed_lights.drain() {
// write zeros in place of this now dead light, the enabled boolean will be set to false
let mut zeros = Vec::new();
zeros.resize(mem::size_of::<LightUniform>(), 0u32);
let offset = mem::size_of::<u32>() * 4 + mem::size_of::<LightUniform>() * v as usize;
queue.write_buffer(&self.buffer, offset as _, bytemuck::cast_slice(zeros.as_slice()));
self.dead_indices.push_back(v);
}
// update the amount of lights, then the array of lights
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[self.current_light_idx as u32]));
} }
} }
@ -216,7 +260,7 @@ pub(crate) enum LightType {
#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct LightUniform { pub(crate) struct LightUniform {
pub position: glam::Vec3, pub position: glam::Vec3,
pub light_type: u32, // LightType pub light_type: u32, // enum LightType
pub direction: glam::Vec3, pub direction: glam::Vec3,
pub enabled: u32, // bool pub enabled: u32, // bool
pub color: glam::Vec3, pub color: glam::Vec3,
@ -225,229 +269,64 @@ pub(crate) struct LightUniform {
pub range: f32, pub range: f32,
pub intensity: f32, pub intensity: f32,
pub smoothness: f32,
pub spot_cutoff: f32, pub spot_cutoff_rad: f32,
pub spot_outer_cutoff: f32, pub spot_outer_cutoff_rad: f32,
pub _padding4: u32,
} }
impl LightUniform { impl LightUniform {
pub fn from_point_light_bundle(light: &PointLight, transform: &Transform) -> Self { pub fn from_point_light_bundle(light: &PointLight, transform: &Transform) -> Self {
Self { Self {
light_type: LightType::Point as u32, light_type: LightType::Point as u32,
enabled: true as u32, // TODO enabled: light.enabled as u32,
position: transform.translation, position: transform.translation,
direction: transform.forward(), direction: transform.forward(),
color: light.color, color: light.color,
range: 1.5, range: light.range,
intensity: 1.0, intensity: light.intensity,
smoothness: light.smoothness,
spot_cutoff: 0.0, spot_cutoff_rad: 0.0,
spot_outer_cutoff: 0.0, spot_outer_cutoff_rad: 0.0,
_padding4: 0,
} }
} }
pub fn from_directional_bundle(light: &DirectionalLight, transform: &Transform) -> Self { pub fn from_directional_bundle(light: &DirectionalLight, transform: &Transform) -> Self {
Self { Self {
light_type: LightType::Directional as u32, light_type: LightType::Directional as u32,
enabled: true as u32, // TODO: take from component enabled: light.enabled as u32,
position: transform.translation, position: transform.translation,
direction: transform.forward(), direction: transform.forward(),
color: light.color, color: light.color,
range: 0.0, range: 0.0,
intensity: 0.0, intensity: light.intensity,
smoothness: 0.0,
spot_cutoff: 0.0, spot_cutoff_rad: 0.0,
spot_outer_cutoff: 0.0, spot_outer_cutoff_rad: 0.0,
_padding4: 0,
} }
} }
// Create the SpotLightUniform from an ECS bundle // Create the SpotLightUniform from an ECS bundle
/* pub fn from_bundle(light: &SpotLight, transform: &Transform) -> Self { pub fn from_spot_light_bundle(light: &SpotLight, transform: &Transform) -> Self {
Self { Self {
light_type: LightType::Spotlight as u32,
enabled: light.enabled as u32,
position: transform.translation, position: transform.translation,
_padding: 0,
direction: transform.forward(), direction: transform.forward(),
_padding2: 0,
color: light.color, color: light.color,
cutoff: light.cutoff.to_radians().cos(),
outer_cutoff: light.outer_cutoff.to_radians().cos(),
constant: light.constant,
linear: light.linear,
quadratic: light.quadratic,
ambient: light.ambient,
diffuse: light.diffuse,
specular: light.specular,
_padding3: 0,
}
} */
}
#[repr(C)] range: light.range,
#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct LightsUniform {
light_count: u32,
_padding: [u32; 3],
data: [LightUniform; 10], // TODO: make this a dynamic length
}
/* #[repr(C)]
#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct LightsUniform {
light_count: u32,
_padding: [u32; 3],
spot_lights: [SpotLightUniform; MAX_LIGHT_COUNT],
spot_light_count: u32,
_padding2: [u32; 3],
directional_light: DirectionalLightUniform,
}
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PointLightUniform {
/// The position of the light
/// vec4 is used here for gpu padding, w is ignored in the shader
pub position: glam::Vec4,
/// The color of the light
/// vec4 is used here for gpu padding, w is ignored in the shader
pub color: glam::Vec4,
/// The intensity of the light
/// This works by just multiplying the result of the lighting
/// calculations by this scalar
pub intensity: f32,
/// The constant used in the quadratic attenuation calculation. Its best to leave this at 1.0
pub constant: f32,
/// The linear factor used in the quadratic attenuation calculation.
pub linear: f32,
/// The quadratic factor used in the quadratic attenuation calculation.
pub quadratic: f32,
pub ambient: f32,
pub diffuse: f32,
pub specular: f32,
pub _padding: u32,
}
impl PointLightUniform {
/// Create the PointLightUniform from an ECS bundle
pub fn from_bundle(light: &PointLight, transform: &Transform) -> Self {
Self {
position: glam::Vec4::new(transform.translation.x, transform.translation.y, transform.translation.z, 0.0),
//_padding: 0,
color: glam::Vec4::new(light.color.x, light.color.y, light.color.z, 0.0),
//_padding2: 0,
intensity: light.intensity, intensity: light.intensity,
constant: light.constant, smoothness: light.smoothness,
linear: light.linear,
quadratic: light.quadratic,
ambient: light.ambient, spot_cutoff_rad: light.cutoff.to_radians(),
diffuse: light.diffuse, spot_outer_cutoff_rad: light.outer_cutoff.to_radians(),
specular: light.specular,
_padding: 0,
} }
} }
} }
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct DirectionalLightUniform {
/// The direction of the light
pub direction: glam::Vec3,
// gpu padding
pub _padding: u32,
/// The color of the light
pub color: glam::Vec3,
// no padding is needed here since ambient acts as the padding
// that would usually be needed for the vec3
/// The scalar of the ambient light created by this caster.
pub ambient: f32,
/// The scalar of the diffuse light created by this caster.
pub diffuse: f32,
/// The scalar of the specular reflections created by this caster.
pub specular: f32,
pub _padding2: [u32; 2],
}
impl DirectionalLightUniform {
/// Create the DirectionalLightUniform from an ECS bundle
pub fn from_bundle(light: &DirectionalLight, transform: &Transform) -> Self {
//transform.forward()
Self {
direction: transform.forward(),
_padding: 0,
color: light.color,
ambient: light.ambient,
diffuse: light.diffuse,
specular: light.specular,
_padding2: [0; 2],
}
}
}
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct SpotLightUniform {
pub position: glam::Vec3,
pub _padding: u32,
pub direction: glam::Vec3,
pub _padding2: u32,
pub color: glam::Vec3,
// no padding is needed here since cutoff acts as the padding
// that would usually be needed for the vec3
pub cutoff: f32,
pub outer_cutoff: f32,
/// The constant used in the quadratic attenuation calculation. Its best to leave this at 1.0
pub constant: f32,
/// The linear factor used in the quadratic attenuation calculation.
pub linear: f32,
/// The quadratic factor used in the quadratic attenuation calculation.
pub quadratic: f32,
pub ambient: f32,
pub diffuse: f32,
pub specular: f32,
pub _padding3: u32,
}
impl SpotLightUniform {
/// Create the SpotLightUniform from an ECS bundle
pub fn from_bundle(light: &SpotLight, transform: &Transform) -> Self {
Self {
position: transform.translation,
_padding: 0,
direction: transform.forward(),
_padding2: 0,
color: light.color,
cutoff: light.cutoff.to_radians().cos(),
outer_cutoff: light.outer_cutoff.to_radians().cos(),
constant: light.constant,
linear: light.linear,
quadratic: light.quadratic,
ambient: light.ambient,
diffuse: light.diffuse,
specular: light.specular,
_padding3: 0,
}
}
} */

View File

@ -1,13 +1,22 @@
use lyra_ecs::Component; use lyra_ecs::Component;
#[derive(Default, Debug, Clone, Component)] #[derive(Debug, Clone, Component)]
pub struct PointLight { pub struct PointLight {
pub enabled: bool,
pub color: glam::Vec3, pub color: glam::Vec3,
pub range: f32,
pub intensity: f32, pub intensity: f32,
pub constant: f32, pub smoothness: f32,
pub linear: f32, }
pub quadratic: f32,
pub ambient: f32, impl Default for PointLight {
pub diffuse: f32, fn default() -> Self {
pub specular: f32, Self {
enabled: true,
color: glam::Vec3::new(1.0, 1.0, 1.0),
range: 1.0,
intensity: 1.0,
smoothness: 0.75,
}
}
} }

View File

@ -4,15 +4,26 @@ use crate::math;
#[derive(Debug, Clone, Component)] #[derive(Debug, Clone, Component)]
pub struct SpotLight { pub struct SpotLight {
pub enabled: bool,
pub color: glam::Vec3, pub color: glam::Vec3,
pub range: f32,
pub intensity: f32,
pub smoothness: f32,
pub cutoff: math::Angle, pub cutoff: math::Angle,
pub outer_cutoff: math::Angle, pub outer_cutoff: math::Angle,
}
pub constant: f32,
pub linear: f32, impl Default for SpotLight {
pub quadratic: f32, fn default() -> Self {
Self {
pub ambient: f32, enabled: true,
pub diffuse: f32, color: glam::Vec3::new(1.0, 1.0, 1.0),
pub specular: f32, range: 1.0,
intensity: 1.0,
smoothness: 0.75,
cutoff: math::Angle::Degrees(45.0),
outer_cutoff: math::Angle::Degrees(45.0),
}
}
} }

View File

@ -6,6 +6,7 @@ use winit::dpi::PhysicalSize;
use super::{light::LightUniformBuffers, render_buffer::{BindGroupPair, BufferWrapper}, texture::RenderTexture}; use super::{light::LightUniformBuffers, render_buffer::{BindGroupPair, BufferWrapper}, texture::RenderTexture};
#[allow(dead_code)]
pub(crate) struct LightIndicesGridBuffer { pub(crate) struct LightIndicesGridBuffer {
index_counter_buffer: wgpu::Buffer, index_counter_buffer: wgpu::Buffer,
indices_buffer: wgpu::Buffer, indices_buffer: wgpu::Buffer,
@ -82,7 +83,6 @@ impl LightCullCompute {
label: Some("BGL_LightIndicesGrid"), label: Some("BGL_LightIndicesGrid"),
}); });
// TODO: resize texture when screen is resized
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: workgroup_size.x, width: workgroup_size.x,
height: workgroup_size.y, height: workgroup_size.y,
@ -167,7 +167,8 @@ impl LightCullCompute {
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader_src)), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader_src)),
}); });
let workgroup_size = glam::UVec2::new((screen_size.width as f32 / 16.0).ceil() as u32, (screen_size.height as f32 / 16.0).ceil() as u32); let workgroup_size = glam::UVec2::new((screen_size.width as f32 / 16.0).ceil() as u32,
(screen_size.height as f32 / 16.0).ceil() as u32);
let light_grid = Self::create_grid(&device, workgroup_size); let light_grid = Self::create_grid(&device, workgroup_size);
let depth_tex_pair = depth_texture.create_bind_group(&device); let depth_tex_pair = depth_texture.create_bind_group(&device);
@ -204,7 +205,11 @@ impl LightCullCompute {
pub fn update_screen_size(&mut self, size: PhysicalSize<u32>) { pub fn update_screen_size(&mut self, size: PhysicalSize<u32>) {
self.screen_size_buffer.write_buffer(&self.queue, 0, self.screen_size_buffer.write_buffer(&self.queue, 0,
&[UVec2::new(size.width, size.height)]); &[UVec2::new(size.width, size.height)]);
self.workgroup_size = glam::UVec2::new((size.width as f32 / 16.0).ceil() as u32, (size.height as f32 / 16.0).ceil() as u32); self.workgroup_size = glam::UVec2::new((size.width as f32 / 16.0).ceil() as u32,
(size.height as f32 / 16.0).ceil() as u32);
// I hate that the entire bind group is recreated on a resize but its the only way :(
self.light_indices_grid = Self::create_grid(&self.device, self.workgroup_size);
} }
pub fn compute(&mut self, camera_buffers: &BufferWrapper, lights_buffers: &LightUniformBuffers, depth_texture: &RenderTexture) { pub fn compute(&mut self, camera_buffers: &BufferWrapper, lights_buffers: &LightUniformBuffers, depth_texture: &RenderTexture) {
@ -227,6 +232,7 @@ impl LightCullCompute {
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1); pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
} }
self.queue.submit(std::iter::once(encoder.finish())); self.queue.submit(std::iter::once(encoder.finish()));
self.device.poll(wgpu::Maintain::Wait); self.device.poll(wgpu::Maintain::Wait);
self.cleanup(); self.cleanup();

View File

@ -35,6 +35,7 @@ struct Light {
range: f32, range: f32,
intensity: f32, intensity: f32,
smoothness: f32,
spot_cutoff: f32, spot_cutoff: f32,
spot_outer_cutoff: f32, spot_outer_cutoff: f32,
@ -200,7 +201,7 @@ fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light:
diffuse_color *= dir_light.diffuse; diffuse_color *= dir_light.diffuse;
specular_color *= dir_light.specular;*/ specular_color *= dir_light.specular;*/
return ambient_color + diffuse_color + specular_color; return (ambient_color + diffuse_color + specular_color) * dir_light.intensity;
} }
fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> { fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> {
@ -226,22 +227,8 @@ fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_li
var specular_color = specular_strength * (light_color * specular_factor); var specular_color = specular_strength * (light_color * specular_factor);
//// end of specular //// //// end of specular ////
// TODO: Point light range
let distance = length(light_pos - world_pos); let distance = length(light_pos - world_pos);
// TODO: make smoothness in this a configurable value let attenuation = 1.0 - smoothstep(point_light.range * point_light.smoothness, point_light.range, distance);
// 0.75 is the smoothness or falloff
let attenuation = 1.0 - smoothstep(point_light.range * 0.75, point_light.range, distance);
//// point light attenuation ////
/*let distance = length(light_pos - world_pos);
let attenuation = 1.0 / (point_light.constant + point_light.linear * distance +
point_light.quadratic * (distance * distance));
//// end of point light attenuation ////
ambient_color *= point_light.ambient * attenuation;
diffuse_color *= point_light.diffuse * attenuation;
specular_color *= point_light.specular * attenuation;*/
ambient_color *= attenuation; ambient_color *= attenuation;
diffuse_color *= attenuation; diffuse_color *= attenuation;
@ -251,5 +238,50 @@ fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_li
} }
fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> { fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> {
return vec3<f32>(0.0); // TODO let light_color = spot_light.color;
let light_pos = spot_light.position;
let camera_view_pos = u_camera.position;
let light_dir = normalize(spot_light.position - world_pos);
var ambient_color = light_color * material.ambient.xyz * material.diffuse.xyz;
//// diffuse ////
//let light_dir = normalize(light_pos - world_pos);
let diffuse_strength = max(dot(world_norm, light_dir), 0.0);
var diffuse_color = light_color * (diffuse_strength * material.diffuse.xyz);
//// end of diffuse ////
//// specular ////
let view_dir = normalize(camera_view_pos - world_pos);
let half_dir = normalize(view_dir + light_dir);
let specular_strength = pow(max(dot(world_norm, half_dir), 0.0), material.shininess);
var specular_color = specular_strength * (light_color * specular_factor);
//// end of specular ////
//// spot light soft edges ////
let theta = dot(light_dir, normalize(-spot_light.direction));
let epsilon = spot_light.spot_cutoff - spot_light.spot_outer_cutoff;
let intensity = clamp((theta - spot_light.spot_outer_cutoff) / epsilon, 0.0, 1.0);
//diffuse_color *= intensity;
//specular_color *= intensity;
//// end of spot light soft edges ////
//// spot light attenuation ////
let distance = length(light_pos - world_pos);
let attenuation = calc_attenuation(spot_light, distance);
ambient_color *= attenuation * intensity;
diffuse_color *= attenuation * intensity;
specular_color *= attenuation * intensity;
//// end of spot light attenuation ////
return /*ambient_color +*/ diffuse_color + specular_color;
}
fn calc_attenuation(light: Light, distance: f32) -> f32 {
return 1.0 - smoothstep(light.range * light.smoothness, light.range, distance);
} }

View File

@ -22,6 +22,7 @@ struct Light {
range: f32, range: f32,
intensity: f32, intensity: f32,
smoothness: f32,
spot_cutoff: f32, spot_cutoff: f32,
spot_outer_cutoff: f32, spot_outer_cutoff: f32,
@ -32,6 +33,13 @@ struct Lights {
data: array<Light>, data: array<Light>,
}; };
struct Cone {
tip: vec3<f32>,
height: f32,
direction: vec3<f32>,
radius: f32,
}
var<workgroup> wg_min_depth: atomic<u32>; var<workgroup> wg_min_depth: atomic<u32>;
var<workgroup> wg_max_depth: atomic<u32>; var<workgroup> wg_max_depth: atomic<u32>;
var<workgroup> wg_light_index_start: atomic<u32>; var<workgroup> wg_light_index_start: atomic<u32>;
@ -159,13 +167,22 @@ fn cs_main(
&& sphere_inside_frustrum(wg_frustum_planes, position, radius)) { && sphere_inside_frustrum(wg_frustum_planes, position, radius)) {
// TODO: add the light to the transparent geometry list // TODO: add the light to the transparent geometry list
add_light(light_index);
if (!sphere_inside_plane(position, radius, wg_frustum_planes[4])) { if (!sphere_inside_plane(position, radius, wg_frustum_planes[4])) {
add_light(light_index);
}
} else if (light.light_ty == LIGHT_TY_SPOT) {
let dir_vs = u_camera.view * vec4<f32>(light.direction, 1.0);
let cone_radius = tan(light.spot_cutoff) * light.range;
let cone = Cone(position, radius, dir_vs.xyz, cone_radius);
if (cone_inside_frustum(cone, wg_frustum_planes)) {
// TODO: add the light to the transparent geometry list
if (!cone_inside_plane(cone, wg_frustum_planes[4])) {
add_light(light_index);
}
} }
} }
// TODO: spotlights
} }
} }
@ -258,3 +275,44 @@ fn compute_plane(p0: vec3<f32>, p1: vec3<f32>, p2: vec3<f32>) -> vec4<f32> {
return plane; return plane;
} }
fn point_inside_plane(point: vec3<f32>, plane: vec4<f32>) -> bool {
return dot(plane.xyz, point) - plane.w < 0.0;
}
/// Check to see if a cone if fully behind (inside the negative halfspace of) a plane.
///
/// Source: Real-time collision detection, Christer Ericson (2005)
/// (https://www.3dgep.com/forward-plus/#light-culling-compute-shader)
fn cone_inside_plane(cone: Cone, plane: vec4<f32>) -> bool {
// Compute the farthest point on the end of the cone to the positive space of the plane.
let m = cross(cross(plane.xyz, cone.direction), cone.direction);
let farthest = cone.tip + cone.direction * cone.height - m * cone.radius;
// The cone is in the negative halfspace of the plane if the tip of the cone,
// and the farthest point on the end of the cone are inside the negative halfspace
// of the plane.
return point_inside_plane(cone.tip, plane) && point_inside_plane(farthest, plane);
}
fn cone_inside_frustum(cone: Cone, frustum: array<vec4<f32>, 6>) -> bool {
//let near_plane = frustum[4];
//let far_plane = frustum[5];
// check near and far clipping planes first
//if (cone_inside_plane(cone, near_plane) || cone_inside_plane(cone, far_plane)) {
// return false;
//}
// to be able to index this array with a non-const value,
// it must be defined as a var
var frustum_v = frustum;
for (var i = 0u; i < 4u; i++) {
if (cone_inside_plane(cone, frustum_v[i])) {
return false;
}
}
return true;
}

View File

@ -15,13 +15,19 @@ in
mold mold
udev udev
lua5_4_compat lua5_4_compat
(nixpkgs.rustChannelOf { rustToolchain = ./rust-toolchain.toml; }).rust ((nixpkgs.rustChannelOf { rustToolchain = ./rust-toolchain.toml; }).rust.override {
extensions = [
"rust-src"
"rust-analysis"
];
})
]; ];
buildInputs = [ buildInputs = [
udev alsa-lib libGL gcc udev alsa-lib libGL gcc
vulkan-loader vulkan-headers vulkan-tools vulkan-loader vulkan-headers vulkan-tools
xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature
libxkbcommon wayland # To use the wayland feature libxkbcommon wayland # To use the wayland feature
]; ];
LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
} }