Compare commits

...

2 Commits

12 changed files with 911 additions and 332 deletions

18
.vscode/launch.json vendored
View File

@ -22,6 +22,24 @@
"args": [], "args": [],
"cwd": "${workspaceFolder}/examples/testbed" "cwd": "${workspaceFolder}/examples/testbed"
}, },
{
"type": "lldb",
"request": "launch",
"name": "Debug lyra shadows",
"cargo": {
"args": [
"build",
"--manifest-path", "${workspaceFolder}/examples/shadows/Cargo.toml"
//"--bin=shadows",
],
"filter": {
"name": "shadows",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}/examples/shadows"
},
{ {
"type": "lldb", "type": "lldb",
"request": "launch", "request": "launch",

7
Cargo.lock generated
View File

@ -1881,7 +1881,6 @@ dependencies = [
"lyra-scene", "lyra-scene",
"petgraph", "petgraph",
"quote", "quote",
"rectangle-pack",
"round_mult", "round_mult",
"rustc-hash", "rustc-hash",
"syn 2.0.51", "syn 2.0.51",
@ -2769,12 +2768,6 @@ dependencies = [
"rand_core 0.3.1", "rand_core 0.3.1",
] ]
[[package]]
name = "rectangle-pack"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.3.5" version = "0.3.5"

View File

@ -34,4 +34,4 @@ lyra-scripting = { path = "lyra-scripting", optional = true }
#opt-level = 1 #opt-level = 1
[profile.release] [profile.release]
debug = true debug = true

View File

@ -6,7 +6,7 @@ use lyra_engine::{
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
}, },
math::{self, Transform, Vec3}, math::{self, Transform, Vec3},
render::light::directional::DirectionalLight, render::light::{directional::DirectionalLight, PointLight},
scene::{ scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
@ -130,12 +130,14 @@ fn setup_scene_plugin(game: &mut Game) {
drop(resman); drop(resman);
// cube in the air
world.spawn(( world.spawn((
cube_mesh.clone(), cube_mesh.clone(),
WorldTransform::default(), WorldTransform::default(),
Transform::from_xyz(0.0, -2.0, -5.0), Transform::from_xyz(0.0, -2.0, -5.0),
)); ));
// cube on the right, on the ground
world.spawn(( world.spawn((
cube_mesh.clone(), cube_mesh.clone(),
WorldTransform::default(), WorldTransform::default(),
@ -159,10 +161,34 @@ fn setup_scene_plugin(game: &mut Game) {
DirectionalLight { DirectionalLight {
enabled: true, enabled: true,
color: Vec3::new(1.0, 0.95, 0.9), color: Vec3::new(1.0, 0.95, 0.9),
intensity: 1.0, intensity: 0.5,
}, },
light_tran, light_tran,
)); ));
world.spawn((
//cube_mesh.clone(),
PointLight {
enabled: true,
color: Vec3::new(0.133, 0.098, 0.91),
intensity: 2.0,
range: 9.0,
..Default::default()
},
Transform::from_xyz(5.0, -2.5, -3.3),
));
world.spawn((
//cube_mesh.clone(),
PointLight {
enabled: true,
color: Vec3::new(0.278, 0.984, 0.0),
intensity: 2.0,
range: 9.0,
..Default::default()
},
Transform::from_xyz(-0.5, 2.0, -5.0),
));
} }
let mut camera = CameraComponent::new_3d(); let mut camera = CameraComponent::new_3d();

View File

@ -38,7 +38,6 @@ unique = "0.9.1"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
petgraph = { version = "0.6.5", features = ["matrix_graph"] } petgraph = { version = "0.6.5", features = ["matrix_graph"] }
bind_match = "0.1.2" bind_match = "0.1.2"
rectangle-pack = "0.4.2"
round_mult = "0.1.3" round_mult = "0.1.3"
[features] [features]

View File

@ -1,7 +1,6 @@
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
mem, mem,
num::NonZeroU64,
rc::Rc, rc::Rc,
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
}; };
@ -11,18 +10,18 @@ use lyra_ecs::{
AtomicRef, Component, Entity, ResourceData, AtomicRef, Component, Entity, ResourceData,
}; };
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
use lyra_math::Transform; use lyra_math::{Angle, Transform};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use tracing::{debug, warn}; use tracing::warn;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use crate::render::{ use crate::render::{
graph::{Node, NodeDesc, NodeType, SlotAttribute, SlotValue}, graph::{Node, NodeDesc, NodeType, SlotAttribute, SlotValue},
light::directional::DirectionalLight, light::{directional::DirectionalLight, LightType, PointLight},
resource::{RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
transform_buffer_storage::TransformBuffers, transform_buffer_storage::TransformBuffers,
vertex::Vertex, vertex::Vertex,
AtlasViewport, GpuSlotBuffer, TextureAtlas, AtlasFrame, GpuSlotBuffer, TextureAtlas,
}; };
use super::{MeshBufferStorage, RenderAssets, RenderMeshes}; use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
@ -43,10 +42,15 @@ pub struct ShadowMapsPassLabel;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct LightDepthMap { struct LightDepthMap {
//light_projection_buffer: Arc<wgpu::Buffer>, /// The type of the light that this map is created for.
//bindgroup: wgpu::BindGroup, light_type: LightType,
/// The index of the first shadow depth map.
///
/// If the light is a point light, this is the index of the FIRST depth map in the atlas with
/// the maps of the other sides following the index.
atlas_index: u64, atlas_index: u64,
uniform_index: u64, /// The index of the uniform for the light in the uniform array.
uniform_index: [u64; 6],
} }
pub struct ShadowMapsPass { pub struct ShadowMapsPass {
@ -63,6 +67,7 @@ pub struct ShadowMapsPass {
render_meshes: Option<ResourceData>, render_meshes: Option<ResourceData>,
mesh_buffers: Option<ResourceData>, mesh_buffers: Option<ResourceData>,
pipeline: Option<RenderPipeline>, pipeline: Option<RenderPipeline>,
point_light_pipeline: Option<RenderPipeline>,
atlas: LightShadowMapAtlas, atlas: LightShadowMapAtlas,
/// The depth map atlas sampler /// The depth map atlas sampler
@ -79,10 +84,8 @@ impl ShadowMapsPass {
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer { ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true }, ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: true, has_dynamic_offset: false,
min_binding_size: Some( min_binding_size: None,
NonZeroU64::new(mem::size_of::<LightShadowUniform>() as _).unwrap(),
),
}, },
count: None, count: None,
}], }],
@ -93,7 +96,7 @@ impl ShadowMapsPass {
device, device,
wgpu::TextureFormat::Depth32Float, wgpu::TextureFormat::Depth32Float,
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
SHADOW_SIZE * 4, SHADOW_SIZE * 8,
); );
let atlas_size_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let atlas_size_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
@ -116,12 +119,11 @@ impl ShadowMapsPass {
let cap = device.limits().max_storage_buffer_binding_size as u64 let cap = device.limits().max_storage_buffer_binding_size as u64
/ mem::size_of::<LightShadowUniform>() as u64; / mem::size_of::<LightShadowUniform>() as u64;
let uniforms_buffer = GpuSlotBuffer::new_aligned( let uniforms_buffer = GpuSlotBuffer::new(
device, device,
Some("buffer_shadow_maps_light"), Some("buffer_shadow_maps_light"),
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
cap, cap,
256,
); );
let uniforms_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { let uniforms_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
@ -132,7 +134,7 @@ impl ShadowMapsPass {
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: uniforms_buffer.buffer(), buffer: uniforms_buffer.buffer(),
offset: 0, offset: 0,
size: Some(NonZeroU64::new(mem::size_of::<LightShadowUniform>() as _).unwrap()), size: None,
}), }),
}], }],
}); });
@ -147,6 +149,7 @@ impl ShadowMapsPass {
render_meshes: None, render_meshes: None,
mesh_buffers: None, mesh_buffers: None,
pipeline: None, pipeline: None,
point_light_pipeline: None,
atlas_sampler: Rc::new(sampler), atlas_sampler: Rc::new(sampler),
atlas: LightShadowMapAtlas(Arc::new(RwLock::new(atlas))), atlas: LightShadowMapAtlas(Arc::new(RwLock::new(atlas))),
@ -157,41 +160,156 @@ impl ShadowMapsPass {
fn create_depth_map( fn create_depth_map(
&mut self, &mut self,
queue: &wgpu::Queue, queue: &wgpu::Queue,
light_type: LightType,
entity: Entity, entity: Entity,
light_pos: Transform, light_pos: Transform,
far_plane: f32,
) -> LightDepthMap { ) -> LightDepthMap {
const NEAR_PLANE: f32 = 0.1; const NEAR_PLANE: f32 = 0.1;
const FAR_PLANE: f32 = 45.0; const FAR_PLANE: f32 = 45.0;
let mut atlas = self.atlas.get_mut(); let mut atlas = self.atlas.get_mut();
let atlas_index = atlas
.pack_new_texture(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.expect("failed to pack new shadow map into texture atlas");
let atlas_frame = atlas.texture_viewport(atlas_index);
let ortho_proj = let (start_atlas_idx, uniform_indices) = match light_type {
glam::Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, NEAR_PLANE, FAR_PLANE); LightType::Directional => {
// directional lights require a single map, so allocate that in the atlas.
let atlas_index = atlas
.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.expect("failed to pack new shadow map into texture atlas");
let atlas_frame = atlas.texture_frame(atlas_index)
.expect("Frame missing");
let look_view = let projection =
glam::Mat4::look_to_rh(light_pos.translation, light_pos.forward(), light_pos.up()); glam::Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, NEAR_PLANE, FAR_PLANE);
let look_view = glam::Mat4::look_to_rh(
light_pos.translation,
light_pos.forward(),
light_pos.up(),
);
let light_proj = ortho_proj * look_view; let light_proj = projection * look_view;
let uniform = LightShadowUniform {
space_mat: light_proj, let u = LightShadowUniform {
atlas_frame, space_mat: light_proj,
atlas_frame,
near_plane: NEAR_PLANE,
far_plane,
_padding1: [0; 2],
light_pos: light_pos.translation,
_padding2: 0,
};
let uniform_index = self.light_uniforms_buffer.insert(queue, &u);
let mut indices = [0; 6];
indices[0] = uniform_index;
(atlas_index, indices)
}
LightType::Spotlight => todo!(),
LightType::Point => {
let aspect = SHADOW_SIZE.x as f32 / SHADOW_SIZE.y as f32;
let projection = glam::Mat4::perspective_rh(
Angle::Degrees(90.0).to_radians(),
aspect,
NEAR_PLANE,
far_plane,
);
let light_trans = light_pos.translation;
// right, left, top, bottom, near, and far
let views = [
projection
* glam::Mat4::look_at_rh(
light_trans,
light_trans + glam::vec3(1.0, 0.0, 0.0),
glam::vec3(0.0, -1.0, 0.0),
),
projection
* glam::Mat4::look_at_rh(
light_trans,
light_trans + glam::vec3(-1.0, 0.0, 0.0),
glam::vec3(0.0, -1.0, 0.0),
),
projection
* glam::Mat4::look_at_rh(
light_trans,
light_trans + glam::vec3(0.0, 1.0, 0.0),
glam::vec3(0.0, 0.0, 1.0),
),
projection
* glam::Mat4::look_at_rh(
light_trans,
light_trans + glam::vec3(0.0, -1.0, 0.0),
glam::vec3(0.0, 0.0, -1.0),
),
projection
* glam::Mat4::look_at_rh(
light_trans,
light_trans + glam::vec3(0.0, 0.0, 1.0),
glam::vec3(0.0, -1.0, 0.0),
),
projection
* glam::Mat4::look_at_rh(
light_trans,
light_trans + glam::vec3(0.0, 0.0, -1.0),
glam::vec3(0.0, -1.0, 0.0),
),
];
let atlas_idx_1 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_2 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_3 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_4 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_5 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_6 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let frames = [
atlas.texture_frame(atlas_idx_1).unwrap(),
atlas.texture_frame(atlas_idx_2).unwrap(),
atlas.texture_frame(atlas_idx_3).unwrap(),
atlas.texture_frame(atlas_idx_4).unwrap(),
atlas.texture_frame(atlas_idx_5).unwrap(),
atlas.texture_frame(atlas_idx_6).unwrap(),
];
// create the uniforms of the light, storing them in the gpu buffer, and
// collecting the indices in the buffer they're at.
let mut indices = [0; 6];
for i in 0..6 {
let uniform_i = self.light_uniforms_buffer.insert(
queue,
&LightShadowUniform {
space_mat: views[i],
atlas_frame: frames[i],
near_plane: NEAR_PLANE,
far_plane,
_padding1: [0; 2],
light_pos: light_trans,
_padding2: 0,
},
);
indices[i] = uniform_i;
}
(atlas_idx_1, indices)
}
}; };
/* let uniform_index = self.light_uniforms_index;
self.light_uniforms_index += 1;
//self.light_uniforms_buffer
let offset = uniform_index_offset(&device.limits(), uniform_index);
queue.write_buffer(&self.light_uniforms_buffer, offset as u64, bytemuck::bytes_of(&uniform)); */
let uniform_index = self.light_uniforms_buffer.insert(queue, &uniform);
let v = LightDepthMap { let v = LightDepthMap {
atlas_index, light_type,
uniform_index, atlas_index: start_atlas_idx,
uniform_index: uniform_indices,
}; };
self.depth_maps.insert(entity, v); self.depth_maps.insert(entity, v);
@ -270,14 +388,39 @@ impl Node for ShadowMapsPass {
// use a queue for storing atlas ids to add to entities after the entities are iterated // use a queue for storing atlas ids to add to entities after the entities are iterated
let mut index_components_queue = VecDeque::new(); let mut index_components_queue = VecDeque::new();
/* for (entity, pos, (has_dir, has_point)) in world.view_iter::<(Entities, &Transform, Or<Has<DirectionalLight>, Has<PointLight>>)>() {
if !self.depth_maps.contains_key(&entity) {
// TODO: calculate far plane
let (light_type, far_plane) = if has_dir.is_some() {
(LightType::Directional, 45.0)
} else if has_point.is_some() {
(LightType::Point, 45.0)
} else {
todo!("Spot lights")
};
// TODO: dont pack the textures as they're added
let atlas_index =
self.create_depth_map(&context.queue, light_type, entity, *pos, far_plane);
index_components_queue.push_back((entity, atlas_index));
}
} */
for (entity, pos, _) in world.view_iter::<(Entities, &Transform, Has<DirectionalLight>)>() { for (entity, pos, _) in world.view_iter::<(Entities, &Transform, Has<DirectionalLight>)>() {
if !self.depth_maps.contains_key(&entity) { if !self.depth_maps.contains_key(&entity) {
// TODO: dont pack the textures as they're added // TODO: dont pack the textures as they're added
let atlas_index = let atlas_index =
self.create_depth_map(&context.queue, entity, *pos); self.create_depth_map(&context.queue, LightType::Directional, entity, *pos, 45.0);
index_components_queue.push_back((entity, atlas_index)); index_components_queue.push_back((entity, atlas_index));
}
}
debug!("Created depth map for {:?} light entity", entity); for (entity, pos, _) in world.view_iter::<(Entities, &Transform, Has<PointLight>)>() {
if !self.depth_maps.contains_key(&entity) {
// TODO: dont pack the textures as they're added
let atlas_index =
self.create_depth_map(&context.queue, LightType::Point, entity, *pos, 30.0);
index_components_queue.push_back((entity, atlas_index));
} }
} }
@ -287,7 +430,7 @@ impl Node for ShadowMapsPass {
entity, entity,
LightShadowMapId { LightShadowMapId {
atlas_index: depth.atlas_index, atlas_index: depth.atlas_index,
uniform_index: depth.uniform_index, uniform_indices: depth.uniform_index,
}, },
); );
} }
@ -305,18 +448,14 @@ impl Node for ShadowMapsPass {
&graph.device, &graph.device,
&RenderPipelineDescriptor { &RenderPipelineDescriptor {
label: Some("pipeline_shadows".into()), label: Some("pipeline_shadows".into()),
layouts: vec![bgl, transforms], layouts: vec![bgl.clone(), transforms.clone()],
push_constant_ranges: vec![], push_constant_ranges: vec![],
vertex: VertexState { vertex: VertexState {
module: shader.clone(), module: shader.clone(),
entry_point: "vs_main".into(), entry_point: "vs_main".into(),
buffers: vec![Vertex::position_desc().into()], buffers: vec![Vertex::position_desc().into()],
}, },
fragment: None, /* Some(FragmentState { fragment: None,
module: shader,
entry_point: "fs_main".into(),
targets: vec![],
}), */
depth_stencil: Some(wgpu::DepthStencilState { depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float, format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true, depth_write_enabled: true,
@ -325,6 +464,40 @@ impl Node for ShadowMapsPass {
bias: wgpu::DepthBiasState::default(), bias: wgpu::DepthBiasState::default(),
}), }),
primitive: wgpu::PrimitiveState { primitive: wgpu::PrimitiveState {
//cull_mode: Some(wgpu::Face::Front),
cull_mode: Some(wgpu::Face::Back),
..Default::default()
},
multisample: wgpu::MultisampleState::default(),
multiview: None,
},
));
self.point_light_pipeline = Some(RenderPipeline::create(
&graph.device,
&RenderPipelineDescriptor {
label: Some("pipeline_point_light_shadows".into()),
layouts: vec![bgl, transforms],
push_constant_ranges: vec![],
vertex: VertexState {
module: shader.clone(),
entry_point: "vs_main".into(),
buffers: vec![Vertex::position_desc().into()],
},
fragment: Some(FragmentState {
module: shader,
entry_point: "fs_point_light_main".into(),
targets: vec![],
}),
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
primitive: wgpu::PrimitiveState {
//cull_mode: Some(wgpu::Face::Front),
cull_mode: Some(wgpu::Face::Back), cull_mode: Some(wgpu::Face::Back),
..Default::default() ..Default::default()
}, },
@ -332,7 +505,6 @@ impl Node for ShadowMapsPass {
multiview: None, multiview: None,
}, },
)); ));
/* */
} }
} }
@ -344,107 +516,145 @@ impl Node for ShadowMapsPass {
) { ) {
let encoder = context.encoder.as_mut().unwrap(); let encoder = context.encoder.as_mut().unwrap();
let pipeline = self.pipeline.as_ref().unwrap(); let pipeline = self.pipeline.as_ref().unwrap();
let point_light_pipeline = self.point_light_pipeline.as_ref().unwrap();
let render_meshes = self.render_meshes(); let render_meshes = self.render_meshes();
let mesh_buffers = self.mesh_buffers(); let mesh_buffers = self.mesh_buffers();
let transforms = self.transform_buffers(); let transforms = self.transform_buffers();
debug_assert_eq!( let atlas = self.atlas.get();
self.depth_maps.len(), let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1, label: Some("pass_shadow_map"),
"shadows map pass only supports 1 light" color_attachments: &[],
); depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
let (_, dir_depth_map) = self view: atlas.view(),
.depth_maps depth_ops: Some(wgpu::Operations {
.iter() load: wgpu::LoadOp::Clear(1.0),
.next() store: true,
.expect("missing directional light in scene");
{
let atlas = self.atlas.get();
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("pass_shadow_map"),
color_attachments: &[],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: atlas.view(),
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: true,
}),
stencil_ops: None,
}), }),
}); stencil_ops: None,
pass.set_pipeline(&pipeline); }),
let viewport = atlas.texture_viewport(dir_depth_map.atlas_index); });
debug!(
"Rendering shadow map to viewport: {viewport:?}, uniform index: {}",
dir_depth_map.uniform_index
);
// only render to the light's map in the atlas
pass.set_viewport(
viewport.offset.x as _,
viewport.offset.y as _,
viewport.size.x as _,
viewport.size.y as _,
0.0,
1.0,
);
// only clear the light map in the atlas
pass.set_scissor_rect(
viewport.offset.x,
viewport.offset.y,
viewport.size.x,
viewport.size.y,
);
for job in render_meshes.iter() { for light_depth_map in self.depth_maps.values() {
// get the mesh (containing vertices) and the buffers from storage
let buffers = mesh_buffers.get(&job.mesh_uuid);
if buffers.is_none() {
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
continue;
}
let buffers = buffers.unwrap();
let uniform_index = match light_depth_map.light_type {
self.light_uniforms_buffer LightType::Directional => {
.offset_of(dir_depth_map.uniform_index) as u32; pass.set_pipeline(&pipeline);
pass.set_bind_group(0, &self.uniforms_bg, &[uniform_index]);
// Get the bindgroup for job's transform and bind to it using an offset. let frame = atlas.texture_frame(light_depth_map.atlas_index)
let bindgroup = transforms.bind_group(job.transform_id); .expect("missing atlas frame for light");
let offset = transforms.buffer_offset(job.transform_id);
pass.set_bind_group(1, bindgroup, &[offset]);
// if this mesh uses indices, use them to draw the mesh light_shadow_pass_impl(
if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() { &mut pass,
let indices_len = indices.count() as u32; &self.uniforms_bg,
&render_meshes,
pass.set_vertex_buffer( &mesh_buffers,
buffers.buffer_vertex.slot(), &transforms,
buffers.buffer_vertex.buffer().slice(..), &frame,
light_depth_map.uniform_index[0] as _,
); );
pass.set_index_buffer(indices.buffer().slice(..), *idx_type); },
pass.draw_indexed(0..indices_len, 0, 0..1); LightType::Point => {
} else { pass.set_pipeline(&point_light_pipeline);
let vertex_count = buffers.buffer_vertex.count();
pass.set_vertex_buffer( for side in 0..6 {
buffers.buffer_vertex.slot(), let frame = atlas.texture_frame(light_depth_map.atlas_index + side)
buffers.buffer_vertex.buffer().slice(..), .expect("missing atlas frame of light");
); let ui = light_depth_map.uniform_index[side as usize];
pass.draw(0..vertex_count as u32, 0..1);
} light_shadow_pass_impl(
&mut pass,
&self.uniforms_bg,
&render_meshes,
&mesh_buffers,
&transforms,
&frame,
ui as _,
);
}
},
LightType::Spotlight => todo!(),
} }
} }
} }
} }
fn light_shadow_pass_impl<'a>(
pass: &mut wgpu::RenderPass<'a>,
uniforms_bind_group: &'a wgpu::BindGroup,
render_meshes: &RenderMeshes,
mesh_buffers: &'a RenderAssets<MeshBufferStorage>,
transforms: &'a TransformBuffers,
shadow_atlas_viewport: &AtlasFrame,
uniform_index: u32,
) {
// only render to the light's map in the atlas
pass.set_viewport(
shadow_atlas_viewport.x as _,
shadow_atlas_viewport.y as _,
shadow_atlas_viewport.width as _,
shadow_atlas_viewport.height as _,
0.0,
1.0,
);
// only clear the light map in the atlas
pass.set_scissor_rect(
shadow_atlas_viewport.x as _,
shadow_atlas_viewport.y as _,
shadow_atlas_viewport.width as _,
shadow_atlas_viewport.height as _,
);
for job in render_meshes.iter() {
// get the mesh (containing vertices) and the buffers from storage
let buffers = mesh_buffers.get(&job.mesh_uuid);
if buffers.is_none() {
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
continue;
}
let buffers = buffers.unwrap();
//let uniform_index = light_uniforms_buffer.offset_of(light_depth_map.uniform_index[0]) as u32;
pass.set_bind_group(0, &uniforms_bind_group, &[]);
// Get the bindgroup for job's transform and bind to it using an offset.
let bindgroup = transforms.bind_group(job.transform_id);
let offset = transforms.buffer_offset(job.transform_id);
pass.set_bind_group(1, bindgroup, &[offset]);
// if this mesh uses indices, use them to draw the mesh
if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() {
let indices_len = indices.count() as u32;
pass.set_vertex_buffer(
buffers.buffer_vertex.slot(),
buffers.buffer_vertex.buffer().slice(..),
);
pass.set_index_buffer(indices.buffer().slice(..), *idx_type);
pass.draw_indexed(0..indices_len, 0, uniform_index..uniform_index + 1);
} else {
let vertex_count = buffers.buffer_vertex.count();
pass.set_vertex_buffer(
buffers.buffer_vertex.slot(),
buffers.buffer_vertex.buffer().slice(..),
);
pass.draw(0..vertex_count as u32, uniform_index..uniform_index + 1);
}
}
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct LightShadowUniform { pub struct LightShadowUniform {
space_mat: glam::Mat4, space_mat: glam::Mat4,
atlas_frame: AtlasViewport, // 2xUVec2 (4xf32), so no padding needed atlas_frame: AtlasFrame, // 2xUVec2 (4xf32), so no padding needed
near_plane: f32,
far_plane: f32,
_padding1: [u32; 2],
light_pos: glam::Vec3,
_padding2: u32,
} }
/// A component that stores the ID of a shadow map in the shadow map atlas for the entities. /// A component that stores the ID of a shadow map in the shadow map atlas for the entities.
@ -454,7 +664,7 @@ pub struct LightShadowUniform {
#[derive(Debug, Default, Copy, Clone, Component)] #[derive(Debug, Default, Copy, Clone, Component)]
pub struct LightShadowMapId { pub struct LightShadowMapId {
atlas_index: u64, atlas_index: u64,
uniform_index: u64, uniform_indices: [u64; 6],
} }
impl LightShadowMapId { impl LightShadowMapId {
@ -462,8 +672,8 @@ impl LightShadowMapId {
self.atlas_index self.atlas_index
} }
pub fn uniform_index(&self) -> u64 { pub fn uniform_index(&self, side: usize) -> u64 {
self.uniform_index self.uniform_indices[side]
} }
} }
@ -480,8 +690,3 @@ impl LightShadowMapAtlas {
self.0.write().unwrap() self.0.write().unwrap()
} }
} }
/* fn uniform_index_offset(limits: &wgpu::Limits, uniform_idx: u64) -> u32 {
let t = uniform_idx as u32 % (limits.max_storage_buffer_binding_size / mem::size_of::<LightShadowUniform>() as u32);
t * limits.min_uniform_buffer_offset_alignment
} */

View File

@ -1,12 +1,17 @@
pub mod point;
pub mod directional; pub mod directional;
pub mod point;
pub mod spotlight; pub mod spotlight;
use lyra_ecs::{Entity, Tick, World}; use lyra_ecs::{Entity, Tick, World};
pub use point::*; pub use point::*;
pub use spotlight::*; pub use spotlight::*;
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, sync::Arc}; use std::{
collections::{HashMap, VecDeque},
marker::PhantomData,
mem,
sync::Arc,
};
use crate::math::Transform; use crate::math::Transform;
@ -22,7 +27,7 @@ pub struct LightBuffer<U: Default + bytemuck::Pod + bytemuck::Zeroable> {
/// The max amount of light casters that could fit in this buffer. /// The max amount of light casters that could fit in this buffer.
pub max_count: usize, pub max_count: usize,
/// The amount of light casters that are taking up space in the buffer. /// The amount of light casters that are taking up space in the buffer.
/// ///
/// This means that a light may be inactive in the buffer, by being replaced /// This means that a light may be inactive in the buffer, by being replaced
/// with a default caster as to not affect lighting. Its easier this way than /// with a default caster as to not affect lighting. Its easier this way than
/// to recreate the array and remove the gaps. /// to recreate the array and remove the gaps.
@ -49,15 +54,27 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
} }
/// Update an existing light in the light buffer. /// Update an existing light in the light buffer.
pub fn update_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { pub fn update_light(
let buffer_idx = *self.used_indexes.get(&entity) &mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
light: U,
) {
let buffer_idx = *self
.used_indexes
.get(&entity)
.expect("Entity for Light is not in buffer!"); .expect("Entity for Light is not in buffer!");
lights_buffer[buffer_idx] = light; lights_buffer[buffer_idx] = light;
} }
/// Add a new light to the light buffer. /// Add a new light to the light buffer.
pub fn add_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { pub fn add_light(
&mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
light: U,
) {
let buffer_idx = match self.dead_indexes.pop_front() { let buffer_idx = match self.dead_indexes.pop_front() {
Some(i) => i, Some(i) => i,
None => { None => {
@ -69,15 +86,20 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
assert!(self.buffer_count <= self.max_count); assert!(self.buffer_count <= self.max_count);
i i
}, }
}; };
self.used_indexes.insert(entity, buffer_idx); self.used_indexes.insert(entity, buffer_idx);
self.update_light(lights_buffer, entity, light); self.update_light(lights_buffer, entity, light);
} }
/// Update, or add a new caster, to the light buffer. /// Update, or add a new caster, to the light buffer.
pub fn update_or_add(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { pub fn update_or_add(
&mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
light: U,
) {
if self.used_indexes.contains_key(&entity) { if self.used_indexes.contains_key(&entity) {
self.update_light(lights_buffer, entity, light); self.update_light(lights_buffer, entity, light);
} else { } else {
@ -86,7 +108,11 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
} }
/// Remove a caster from the buffer, returns true if it was removed. /// Remove a caster from the buffer, returns true if it was removed.
pub fn remove_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity) -> bool { pub fn remove_light(
&mut self,
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
entity: Entity,
) -> bool {
if let Some(removed_idx) = self.used_indexes.remove(&entity) { if let Some(removed_idx) = self.used_indexes.remove(&entity) {
self.dead_indexes.push_back(removed_idx); self.dead_indexes.push_back(removed_idx);
//self.current_count -= 1; //self.current_count -= 1;
@ -112,47 +138,37 @@ impl LightUniformBuffers {
// TODO: ensure we dont write over this limit // 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(&wgpu::BufferDescriptor {
&wgpu::BufferDescriptor { label: Some("UBO_Lights"),
label: Some("UBO_Lights"), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, size: max_buffer_sizes,
size: max_buffer_sizes, mapped_at_creation: false,
mapped_at_creation: false, });
}
);
let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry { binding: 0,
binding: 0, visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer {
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true },
ty: wgpu::BufferBindingType::Storage { has_dynamic_offset: false,
read_only: true min_binding_size: None,
},
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}, },
], count: None,
}],
label: Some("BGL_Lights"), label: Some("BGL_Lights"),
}); });
let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bindgroup_layout, layout: &bindgroup_layout,
entries: &[ entries: &[wgpu::BindGroupEntry {
wgpu::BindGroupEntry { binding: 0,
binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
resource: wgpu::BindingResource::Buffer( buffer: &buffer,
wgpu::BufferBinding { offset: 0,
buffer: &buffer, size: None, // use the full buffer
offset: 0, }),
size: None, // use the full buffer }],
}
)
},
],
label: Some("BG_Lights"), label: Some("BG_Lights"),
}); });
@ -168,21 +184,30 @@ impl LightUniformBuffers {
let _ = world_tick; let _ = world_tick;
let mut lights = vec![]; let mut lights = vec![];
for (point_light, transform, shadow_map_id) in world.view_iter::<(&PointLight, &Transform, Option<&LightShadowMapId>)>() { for (point_light, transform, shadow_map_id) in
world.view_iter::<(&PointLight, &Transform, Option<&LightShadowMapId>)>()
{
let shadow_map_id = shadow_map_id.map(|m| m.clone()); let shadow_map_id = shadow_map_id.map(|m| m.clone());
let uniform = LightUniform::from_point_light_bundle(&point_light, &transform, shadow_map_id); let uniform =
LightUniform::from_point_light_bundle(&point_light, &transform, shadow_map_id);
lights.push(uniform); lights.push(uniform);
} }
for (spot_light, transform, shadow_map_id) in world.view_iter::<(&SpotLight, &Transform, Option<&LightShadowMapId>)>() { for (spot_light, transform, shadow_map_id) in
world.view_iter::<(&SpotLight, &Transform, Option<&LightShadowMapId>)>()
{
let shadow_map_id = shadow_map_id.map(|m| m.clone()); let shadow_map_id = shadow_map_id.map(|m| m.clone());
let uniform = LightUniform::from_spot_light_bundle(&spot_light, &transform, shadow_map_id); let uniform =
LightUniform::from_spot_light_bundle(&spot_light, &transform, shadow_map_id);
lights.push(uniform); lights.push(uniform);
} }
for (dir_light, transform, shadow_map_id) in world.view_iter::<(&DirectionalLight, &Transform, Option<&LightShadowMapId>)>() { for (dir_light, transform, shadow_map_id) in
world.view_iter::<(&DirectionalLight, &Transform, Option<&LightShadowMapId>)>()
{
let shadow_map_id = shadow_map_id.map(|m| m.clone()); let shadow_map_id = shadow_map_id.map(|m| m.clone());
let uniform = LightUniform::from_directional_bundle(&dir_light, &transform, shadow_map_id); let uniform =
LightUniform::from_directional_bundle(&dir_light, &transform, shadow_map_id);
lights.push(uniform); lights.push(uniform);
} }
@ -191,7 +216,11 @@ impl LightUniformBuffers {
// write the amount of lights to the buffer, and right after that the list of lights. // write the amount of lights to the buffer, and right after that the list of lights.
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights.len()])); queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights.len()]));
// the size of u32 is multiplied by 4 because of gpu alignment requirements // the size of u32 is multiplied by 4 because of gpu alignment requirements
queue.write_buffer(&self.buffer, mem::size_of::<u32>() as u64 * 4, bytemuck::cast_slice(lights.as_slice())); queue.write_buffer(
&self.buffer,
mem::size_of::<u32>() as u64 * 4,
bytemuck::cast_slice(lights.as_slice()),
);
} }
} }
@ -214,18 +243,22 @@ pub(crate) struct LightUniform {
pub color: glam::Vec3, pub color: glam::Vec3,
// no padding is needed here since range acts as the padding // no padding is needed here since range acts as the padding
// that would usually be needed for the vec3 // that would usually be needed for the vec3
pub range: f32, pub range: f32,
pub intensity: f32, pub intensity: f32,
pub smoothness: f32, pub smoothness: f32,
pub spot_cutoff_rad: f32, pub spot_cutoff_rad: f32,
pub spot_outer_cutoff_rad: f32, pub spot_outer_cutoff_rad: f32,
pub light_shadow_uniform_index: i32, pub light_shadow_uniform_index: [i32; 6],
_padding: [u32; 2],
} }
impl LightUniform { impl LightUniform {
pub fn from_point_light_bundle(light: &PointLight, transform: &Transform, map_id: Option<LightShadowMapId>) -> Self { pub fn from_point_light_bundle(
light: &PointLight,
transform: &Transform,
map_id: Option<LightShadowMapId>,
) -> Self {
Self { Self {
light_type: LightType::Point as u32, light_type: LightType::Point as u32,
enabled: light.enabled as u32, enabled: light.enabled as u32,
@ -239,12 +272,27 @@ impl LightUniform {
spot_cutoff_rad: 0.0, spot_cutoff_rad: 0.0,
spot_outer_cutoff_rad: 0.0, spot_outer_cutoff_rad: 0.0,
light_shadow_uniform_index: map_id.map(|m| m.uniform_index() as i32).unwrap_or(-1), light_shadow_uniform_index: map_id
.map(|m| {
[
m.uniform_index(0) as i32,
m.uniform_index(1) as i32,
m.uniform_index(2) as i32,
m.uniform_index(3) as i32,
m.uniform_index(4) as i32,
m.uniform_index(5) as i32,
]
})
.unwrap_or([-1; 6]),
_padding: [0; 2],
} }
} }
pub fn from_directional_bundle(light: &DirectionalLight, transform: &Transform, map_id: Option<LightShadowMapId>) -> Self { pub fn from_directional_bundle(
light: &DirectionalLight,
transform: &Transform,
map_id: Option<LightShadowMapId>,
) -> Self {
Self { Self {
light_type: LightType::Directional as u32, light_type: LightType::Directional as u32,
enabled: light.enabled as u32, enabled: light.enabled as u32,
@ -258,12 +306,28 @@ impl LightUniform {
spot_cutoff_rad: 0.0, spot_cutoff_rad: 0.0,
spot_outer_cutoff_rad: 0.0, spot_outer_cutoff_rad: 0.0,
light_shadow_uniform_index: map_id.map(|m| m.uniform_index() as i32).unwrap_or(-1), light_shadow_uniform_index: map_id
.map(|m| {
[
m.uniform_index(0) as i32,
m.uniform_index(1) as i32,
m.uniform_index(2) as i32,
m.uniform_index(3) as i32,
m.uniform_index(4) as i32,
m.uniform_index(5) as i32,
]
})
.unwrap_or([-1; 6]),
_padding: [0; 2],
} }
} }
// Create the SpotLightUniform from an ECS bundle // Create the SpotLightUniform from an ECS bundle
pub fn from_spot_light_bundle(light: &SpotLight, transform: &Transform, map_id: Option<LightShadowMapId>) -> Self { pub fn from_spot_light_bundle(
light: &SpotLight,
transform: &Transform,
map_id: Option<LightShadowMapId>,
) -> Self {
Self { Self {
light_type: LightType::Spotlight as u32, light_type: LightType::Spotlight as u32,
enabled: light.enabled as u32, enabled: light.enabled as u32,
@ -277,8 +341,19 @@ impl LightUniform {
spot_cutoff_rad: light.cutoff.to_radians(), spot_cutoff_rad: light.cutoff.to_radians(),
spot_outer_cutoff_rad: light.outer_cutoff.to_radians(), spot_outer_cutoff_rad: light.outer_cutoff.to_radians(),
light_shadow_uniform_index: map_id.map(|m| m.uniform_index() as i32).unwrap_or(-1), light_shadow_uniform_index: map_id
.map(|m| {
[
m.uniform_index(0) as i32,
m.uniform_index(1) as i32,
m.uniform_index(2) as i32,
m.uniform_index(3) as i32,
m.uniform_index(4) as i32,
m.uniform_index(5) as i32,
]
})
.unwrap_or([-1; 6]),
_padding: [0; 2],
} }
} }
} }

View File

@ -23,8 +23,12 @@ struct VertexOutput {
} }
struct TextureAtlasFrame { struct TextureAtlasFrame {
offset: vec2<u32>, /*offset: vec2<u32>,
size: vec2<u32>, size: vec2<u32>,*/
x: u32,
y: u32,
width: u32,
height: u32,
} }
struct TransformData { struct TransformData {
@ -54,7 +58,7 @@ struct Light {
spot_cutoff: f32, spot_cutoff: f32,
spot_outer_cutoff: f32, spot_outer_cutoff: f32,
light_shadow_uniform_index: i32, light_shadow_uniform_index: array<i32, 6>,
}; };
struct Lights { struct Lights {
@ -111,11 +115,9 @@ var s_diffuse: sampler;
struct LightShadowMapUniform { struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>, light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame, atlas_frame: TextureAtlasFrame,
} near_plane: f32,
far_plane: f32,
struct LightShadowMapUniformAligned { light_pos: vec3<f32>,
@size(256)
inner: LightShadowMapUniform
} }
@group(4) @binding(0) @group(4) @binding(0)
@ -130,7 +132,7 @@ var s_shadow_maps_atlas: sampler;
@group(5) @binding(2) @group(5) @binding(2)
var<uniform> u_shadow_maps_atlas_size: vec2<u32>; var<uniform> u_shadow_maps_atlas_size: vec2<u32>;
@group(5) @binding(3) @group(5) @binding(3)
var<storage, read> u_light_shadow: array<LightShadowMapUniformAligned>; var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
@fragment @fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
@ -152,21 +154,22 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let light_offset = tile.x; let light_offset = tile.x;
let light_count = tile.y; let light_count = tile.y;
let atlas_dimensions: vec2<i32> = textureDimensions(t_shadow_maps_atlas); let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
for (var i = 0u; i < light_count; i++) { for (var i = 0u; i < light_count; i++) {
let light_index = u_light_indices[light_offset + i]; let light_index = u_light_indices[light_offset + i];
let light: Light = u_lights.data[light_index]; let light: Light = u_lights.data[light_index];
let light_dir = normalize(-light.direction);
if (light.light_ty == LIGHT_TY_DIRECTIONAL) { if (light.light_ty == LIGHT_TY_DIRECTIONAL) {
let light_dir = normalize(-light.direction); let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index].inner;
let frag_pos_light_space = shadow_u.light_space_matrix * vec4<f32>(in.world_position, 1.0); let frag_pos_light_space = shadow_u.light_space_matrix * vec4<f32>(in.world_position, 1.0);
let shadow = calc_shadow(in.world_normal, light_dir, frag_pos_light_space, atlas_dimensions, shadow_u.atlas_frame); let shadow = calc_shadow_dir_light(in.world_normal, light_dir, frag_pos_light_space, atlas_dimensions, shadow_u.atlas_frame);
light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow); light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} else if (light.light_ty == LIGHT_TY_POINT) { } else if (light.light_ty == LIGHT_TY_POINT) {
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color); let shadow = calc_shadow_point(in.world_position, in.world_normal, light_dir, light, atlas_dimensions);
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} else if (light.light_ty == LIGHT_TY_SPOT) { } else if (light.light_ty == LIGHT_TY_SPOT) {
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color); light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color);
} }
@ -176,9 +179,70 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(light_object_res, object_color.a); return vec4<f32>(light_object_res, object_color.a);
} }
fn calc_shadow(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: vec4<f32>, atlas_dimensions: vec2<i32>, atlas_region: TextureAtlasFrame) -> f32 { /// Get the cube map side index of a 3d texture coord
///
/// 0 -> UNKNOWN
/// 1 -> right
/// 2 -> left
/// 3 -> top
/// 4 -> bottom
/// 5 -> near
/// 6 -> far
fn get_side_idx(tex_coord: vec3<f32>) -> vec3<f32> {
let abs_x = abs(tex_coord.x);
let abs_y = abs(tex_coord.y);
let abs_z = abs(tex_coord.z);
var major_axis: f32 = 0.0;
var cube_idx: i32 = 0;
var res = vec2<f32>(0.0);
// Determine the dominant axis
if (abs_x >= abs_y && abs_x >= abs_z) {
major_axis = tex_coord.x;
if (tex_coord.x > 0.0) {
cube_idx = 1;
res = vec2<f32>(-tex_coord.z, -tex_coord.y);
} else {
cube_idx = 2;
res = vec2<f32>(tex_coord.z, -tex_coord.y);
}
} else if (abs_y >= abs_x && abs_y >= abs_z) {
major_axis = tex_coord.y;
if (tex_coord.y > 0.0) {
cube_idx = 3;
res = vec2<f32>(tex_coord.x, tex_coord.z);
} else {
cube_idx = 4;
res = vec2<f32>(tex_coord.x, -tex_coord.z);
}
} else {
major_axis = tex_coord.z;
if (tex_coord.z > 0.0) {
cube_idx = 5;
res = vec2<f32>(tex_coord.x, -tex_coord.y);
} else {
cube_idx = 6;
res = vec2<f32>(-tex_coord.x, -tex_coord.y);
}
}
res = (res / abs(major_axis) + 1.0) * 0.5;
//res = normalize(res);
//res.y = 1.0-res.y; // invert y because wgsl
//let t = res.x;
//res.x = res.y;
//res.y = 1.0 - t;
res.y = 1.0 - res.y;
//res.x = 1.0 - res.x;
return vec3<f32>(res, f32(cube_idx));
}
fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: vec4<f32>, atlas_dimensions: vec2<i32>, atlas_region: TextureAtlasFrame) -> f32 {
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w; var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is clipped after transforming // for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y; proj_coords.y = -proj_coords.y;
// dont cast shadows outside the light's far plane // dont cast shadows outside the light's far plane
@ -190,8 +254,8 @@ fn calc_shadow(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: ve
let xy_remapped = proj_coords.xy * 0.5 + 0.5; let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// no need to get the y since the maps are square // no need to get the y since the maps are square
let atlas_start = f32(atlas_region.offset.x) / f32(atlas_dimensions.x); let atlas_start = f32(atlas_region.x) / f32(atlas_dimensions.x);
let atlas_end = f32(atlas_region.offset.x + atlas_region.size.x) / f32(atlas_dimensions.x); let atlas_end = f32(atlas_region.x + atlas_region.width) / f32(atlas_dimensions.x);
// lerp the tex coords to the shadow map for this light. // lerp the tex coords to the shadow map for this light.
proj_coords.x = mix(atlas_start, atlas_end, xy_remapped.x); proj_coords.x = mix(atlas_start, atlas_end, xy_remapped.x);
proj_coords.y = mix(atlas_start, atlas_end, xy_remapped.y); proj_coords.y = mix(atlas_start, atlas_end, xy_remapped.y);
@ -204,7 +268,7 @@ fn calc_shadow(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: ve
// must manually apply offset to the texture coords since `textureSampleLevel` requires a // must manually apply offset to the texture coords since `textureSampleLevel` requires a
// const value. // const value.
let offset_coords = proj_coords.xy + (vec2<f32>(atlas_region.offset) / vec2<f32>(atlas_dimensions)); let offset_coords = proj_coords.xy + (vec2<f32>(f32(atlas_region.x), f32(atlas_region.y)) / vec2<f32>(atlas_dimensions));
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, offset_coords, 0.0); let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, offset_coords, 0.0);
let current_depth = proj_coords.z; let current_depth = proj_coords.z;
@ -218,6 +282,52 @@ fn calc_shadow(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: ve
return shadow; return shadow;
} }
fn calc_shadow_point(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light, atlas_dimensions: vec2<i32>) -> f32 {
var frag_to_light = world_pos - light.position;
let temp = get_side_idx(normalize(frag_to_light));
var coords_2d = temp.xy;
let cube_idx = i32(temp.z);
/// if an unknown cube side was returned, something is broken
if cube_idx == 0 {
return 0.0;
}
var indices = light.light_shadow_uniform_index;
let i = indices[cube_idx - 1];
let u: LightShadowMapUniform = u_light_shadow[i];
// get the atlas frame in [0; 1] in the atlas texture
// z is width, w is height
var region_coords = vec4<f32>(f32(u.atlas_frame.x), f32(u.atlas_frame.y), f32(u.atlas_frame.width), f32(u.atlas_frame.height));
region_coords /= f32(atlas_dimensions.x);
// simulate `ClampToBorder`, not creating shadows past the shadow map regions
if (coords_2d.x >= 1.0 || coords_2d.y >= 1.0) {
return 0.0;
}
// get the coords inside of the region
coords_2d.x = mix(region_coords.x, region_coords.x + region_coords.z, coords_2d.x);
coords_2d.y = mix(region_coords.y, region_coords.y + region_coords.w, coords_2d.y);
var closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, coords_2d, 0.0);
let current_depth = length(frag_to_light);
// convert depth from [0; 1] to the original depth value
closest_depth *= u.far_plane;
// use a bias to avoid shadow acne
let bias = max(0.05 * (1.0 - dot(world_normal, light_dir)), 0.005);
var shadow = 0.0;
if current_depth - bias > closest_depth {
shadow = 1.0;
}
return shadow;
}
fn debug_grid(in: VertexOutput) -> vec4<f32> { fn debug_grid(in: VertexOutput) -> vec4<f32> {
let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0; let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0;
let tile_index = vec2<u32>(floor(tile_index_float)); let tile_index = vec2<u32>(floor(tile_index_float));
@ -262,7 +372,7 @@ fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light:
return (ambient_color + (1.0 - shadow) * (diffuse_color + specular_color)) * dir_light.intensity; return (ambient_color + (1.0 - shadow) * (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>, shadow: f32) -> vec3<f32> {
let light_color = point_light.color.xyz; let light_color = point_light.color.xyz;
let light_pos = point_light.position.xyz; let light_pos = point_light.position.xyz;
let camera_view_pos = u_camera.position; let camera_view_pos = u_camera.position;
@ -292,7 +402,7 @@ fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_li
diffuse_color *= attenuation; diffuse_color *= attenuation;
specular_color *= attenuation; specular_color *= attenuation;
return (ambient_color + diffuse_color + specular_color) * point_light.intensity; return (ambient_color + (1.0 - shadow) * (diffuse_color + specular_color)) * point_light.intensity;
} }
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> {

View File

@ -31,6 +31,7 @@ struct Light {
spot_cutoff: f32, spot_cutoff: f32,
spot_outer_cutoff: f32, spot_outer_cutoff: f32,
light_shadow_uniform_index: array<i32, 6>,
}; };
struct Lights { struct Lights {

View File

@ -11,23 +11,54 @@ struct TextureAtlasFrame {
struct LightShadowMapUniform { struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>, light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame, atlas_frame: TextureAtlasFrame,
near_plane: f32,
far_plane: f32,
light_pos: vec3<f32>,
} }
@group(0) @binding(0) @group(0) @binding(0)
var<storage, read> u_light_shadow: LightShadowMapUniform; var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
/*@group(0) @binding(1)
var<uniform> u_light_pos: vec3<f32>;
@group(0) @binding(2)
var<uniform> u_light_far_plane: f32;*/
@group(1) @binding(0) @group(1) @binding(0)
var<uniform> u_model_transform_data: TransformData; var<uniform> u_model_transform_data: TransformData;
struct VertexOutput { struct VertexOutput {
@builtin(position) @builtin(position)
clip_position: vec4<f32>, clip_position: vec4<f32>,
@location(0) world_pos: vec3<f32>,
@location(1) instance_index: u32,
} }
@vertex @vertex
fn vs_main( fn vs_main(
@location(0) position: vec3<f32> @location(0) position: vec3<f32>,
@builtin(instance_index) instance_index: u32,
) -> VertexOutput { ) -> VertexOutput {
let pos = u_light_shadow.light_space_matrix * u_model_transform_data.transform * vec4<f32>(position, 1.0); let world_pos = u_model_transform_data.transform * vec4<f32>(position, 1.0);
return VertexOutput(pos); let pos = u_light_shadow[instance_index].light_space_matrix * world_pos;
return VertexOutput(pos, world_pos.xyz, instance_index);
}
struct FragmentOutput {
@builtin(frag_depth) depth: f32,
}
/// Fragment shader used for point lights (or other perspective lights) to create linear depth
@fragment
fn fs_point_light_main(
in: VertexOutput
) -> FragmentOutput {
let u = u_light_shadow[in.instance_index];
var light_dis = length(in.world_pos - u.light_pos);
// map to [0; 1] range by dividing by far plane
light_dis = light_dis / u.far_plane;
return FragmentOutput(light_dis);
} }

View File

@ -1,4 +1,4 @@
use std::{collections::VecDeque, marker::PhantomData, mem, num::NonZeroU64, sync::Arc}; use std::{collections::VecDeque, marker::PhantomData, mem, sync::Arc};
/// A buffer on the GPU that has persistent indices. /// A buffer on the GPU that has persistent indices.
/// ///
@ -54,12 +54,11 @@ impl<T: bytemuck::Pod + bytemuck::Zeroable> GpuSlotBuffer<T> {
/// Calculates the byte offset in the buffer of the element at `i`. /// Calculates the byte offset in the buffer of the element at `i`.
pub fn offset_of(&self, i: u64) -> u64 { pub fn offset_of(&self, i: u64) -> u64 {
let offset = i * mem::size_of::<T>() as u64;
if let Some(align) = self.alignment { if let Some(align) = self.alignment {
round_mult::up(offset, NonZeroU64::new(align).unwrap()).unwrap() let transform_index = i % self.capacity;
transform_index * align
} else { } else {
offset i * mem::size_of::<T>() as u64
} }
} }

View File

@ -1,10 +1,8 @@
use std::{ use std::{
collections::BTreeMap, cmp::max, collections::HashMap, sync::Arc
sync::Arc,
}; };
use glam::UVec2; use glam::UVec2;
use rectangle_pack::{pack_rects, GroupedRectsToPlace, RectToInsert, RectanglePackOk, TargetBin};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum AtlasPackError { pub enum AtlasPackError {
@ -14,28 +12,33 @@ pub enum AtlasPackError {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct AtlasViewport { pub struct AtlasFrame {
pub offset: UVec2, pub x: u32,
pub size: UVec2, pub y: u32,
pub width: u32,
pub height: u32,
} }
pub struct TextureAtlas { impl AtlasFrame {
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
Self {
x, y, width, height
}
}
}
pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
atlas_size: UVec2, atlas_size: UVec2,
texture_format: wgpu::TextureFormat, texture_format: wgpu::TextureFormat,
texture: Arc<wgpu::Texture>, texture: Arc<wgpu::Texture>,
view: Arc<wgpu::TextureView>, view: Arc<wgpu::TextureView>,
/// The next id of the next texture that will be added to the atlas. packer: P,
next_texture_id: u64,
rects: GroupedRectsToPlace<u64>,
bins: BTreeMap<u64, TargetBin>,
placement: Option<RectanglePackOk<u64, u64>>,
} }
impl TextureAtlas { impl<P: AtlasPacker> TextureAtlas<P> {
pub fn new( pub fn new(
device: &wgpu::Device, device: &wgpu::Device,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
@ -58,98 +61,30 @@ impl TextureAtlas {
}); });
let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut bins = BTreeMap::new();
// max_depth=1 for 2d
bins.insert(0, TargetBin::new(atlas_size.x, atlas_size.y, 1));
Self { Self {
atlas_size, atlas_size,
texture_format: format, texture_format: format,
texture: Arc::new(texture), texture: Arc::new(texture),
view: Arc::new(view), view: Arc::new(view),
next_texture_id: 0, packer: P::new(atlas_size),
rects: GroupedRectsToPlace::new(),
bins,
placement: None,
} }
} }
/// Add a texture of `size` and pack it into the atlas, returning the id of the texture in /// Add a texture of `size` and pack it into the atlas, returning the id of the texture in
/// the atlas. /// the atlas.
/// ///
/// If you are adding multiple textures at a time and want to wait to pack the atlas, use /// If you are adding multiple textures at a time and want to wait to pack the atlas, use
/// [`TextureAtlas::add_texture_unpacked`] and then after you're done adding them, pack them /// [`TextureAtlas::add_texture_unpacked`] and then after you're done adding them, pack them
/// with [`TextureAtlas::pack_atlas`]. /// with [`TextureAtlas::pack_atlas`].
pub fn pack_new_texture(&mut self, width: u32, height: u32) -> Result<u64, AtlasPackError> { pub fn pack(&mut self, width: u32, height: u32) -> Result<u64, AtlasPackError> {
let id = self.next_texture_id; let id = self.packer.pack(width, height)?;
self.next_texture_id += 1;
// for 2d rects, set depth to 1 Ok(id as u64)
let r = RectToInsert::new(width, height, 1);
self.rects.push_rect(id, None, r);
self.pack_atlas()?;
Ok(id)
}
/// Add a new texture and **DO NOT** pack it into the atlas.
///
/// <div class="warning">
///
/// The texture will not be packed into the atlas meaning
/// [`TextureAtlas::texture_viewport`] will return `None`. To pack the texture,
/// use [`TextureAtlas::pack_atlas`] or use [`TextureAtlas::pack_new_texture`]
/// when only adding a single texture.
///
/// </div>
pub fn add_texture_unpacked(&mut self, width: u32, height: u32) -> Result<u64, AtlasPackError> {
let id = self.next_texture_id;
self.next_texture_id += 1;
// for 2d rects, set depth to 1
let r = RectToInsert::new(width, height, 1);
self.rects.push_rect(id, None, r);
self.pack_atlas()?;
Ok(id)
}
/// Pack the textures into the atlas.
pub fn pack_atlas(&mut self) -> Result<(), AtlasPackError> {
let placement = pack_rects(
&self.rects,
&mut self.bins,
&rectangle_pack::volume_heuristic,
&rectangle_pack::contains_smallest_box,
)
.map_err(|e| match e {
rectangle_pack::RectanglePackError::NotEnoughBinSpace => AtlasPackError::NotEnoughSpace,
})?;
self.placement = Some(placement);
Ok(())
} }
/// Get the viewport of a texture index in the atlas. /// Get the viewport of a texture index in the atlas.
pub fn texture_viewport(&self, atlas_index: u64) -> AtlasViewport { pub fn texture_frame(&self, atlas_index: u64) -> Option<AtlasFrame> {
let locations = self.placement.as_ref().unwrap().packed_locations(); self.packer.frame(atlas_index as _)
let (bin_id, loc) = locations
.get(&atlas_index)
.expect("atlas index is incorrect");
debug_assert_eq!(*bin_id, 0, "somehow the texture was put in some other bin");
AtlasViewport {
offset: UVec2 {
x: loc.x(),
y: loc.y(),
},
size: UVec2 {
x: loc.width(),
y: loc.height(),
},
}
} }
pub fn view(&self) -> &Arc<wgpu::TextureView> { pub fn view(&self) -> &Arc<wgpu::TextureView> {
@ -164,12 +99,199 @@ impl TextureAtlas {
&self.texture_format &self.texture_format
} }
pub fn total_texture_count(&self) -> u64 {
self.next_texture_id // starts at zero, so no need to increment
}
/// Returns the size of the entire texture atlas. /// Returns the size of the entire texture atlas.
pub fn atlas_size(&self) -> UVec2 { pub fn atlas_size(&self) -> UVec2 {
self.atlas_size self.atlas_size
} }
} }
pub trait AtlasPacker {
fn new(size: UVec2) -> Self;
/// Get an [`AtlasFrame`] of a texture with `id`.
fn frame(&self, id: usize) -> Option<AtlasFrame>;
/// Get all [`AtlasFrame`]s in the atlas.
fn frames(&self) -> &HashMap<usize, AtlasFrame>;
/// Pack a new rect into the atlas.
fn pack(&mut self, width: u32, height: u32) -> Result<usize, AtlasPackError>;
}
struct Skyline {
/// Starting x of the skyline
x: usize,
/// Starting y of the skyline
y: usize,
/// Width of the skyline
width: usize,
}
impl Skyline {
fn right(&self) -> usize {
self.x + self.width
}
}
pub struct SkylinePacker {
size: UVec2,
skylines: Vec<Skyline>,
frame_idx: usize,
frames: HashMap<usize, AtlasFrame>,
}
impl SkylinePacker {
pub fn new(size: UVec2) -> Self {
let skylines = vec![Skyline {
x: 0,
y: 0,
width: size.x as _,
}];
Self {
size,
skylines,
frame_idx: 0,
frames: Default::default(),
}
}
fn can_add(&self, mut i: usize, w: u32, h: u32) -> Option<usize> {
let x = self.skylines[i].x as u32;
if x + w > self.size.x {
return None;
}
let mut width_left = w;
let mut y = self.skylines[i].y as u32;
loop {
y = max(y, self.skylines[i].y as u32);
if y + h > self.size.y {
return None;
}
if self.skylines[i].width as u32 > width_left {
return Some(y as usize);
}
width_left -= self.skylines[i].width as u32;
i += 1;
if i >= self.skylines.len() {
return None;
}
}
}
fn find_skyline(&self, width: u32, height: u32) -> Option<(usize, AtlasFrame)> {
let mut min_height = std::u32::MAX;
let mut min_width = std::u32::MAX;
let mut index = None;
let mut frame = AtlasFrame::default();
// keep the min height as small as possible
for i in 0..self.skylines.len() {
if let Some(y) = self.can_add(i, width, height) {
let y = y as u32;
/* if r.bottom() < min_height
|| (r.bottom() == min_height && self.skylines[i].width < min_width as usize) */
if y + height < min_height ||
(y + height == min_height && self.skylines[i].width < min_width as _)
{
min_height = y + height;
min_width = self.skylines[i].width as _;
index = Some(i);
frame = AtlasFrame::new(self.skylines[i].x as _, y, width, height);
}
}
// TODO: rotation
}
if let Some(index) = index {
Some((index, frame))
} else {
None
}
}
fn split(&mut self, i: usize, frame: &AtlasFrame) {
let skyline = Skyline {
x: frame.x as _,
y: (frame.y + frame.height) as _,
width: frame.width as _
};
assert!(skyline.right() <= self.size.x as _);
assert!(skyline.y <= self.size.y as _);
self.skylines.insert(i, skyline);
let i = i + 1;
while i < self.skylines.len() {
assert!(self.skylines[i - 1].x <= self.skylines[i].x);
if self.skylines[i].x < self.skylines[i - 1].x + self.skylines[i - 1].width {
let shrink = self.skylines[i-1].x + self.skylines[i-1].width - self.skylines[i].x;
if self.skylines[i].width <= shrink {
self.skylines.remove(i);
} else {
self.skylines[i].x += shrink;
self.skylines[i].width -= shrink;
break;
}
} else {
break;
}
}
}
/// Merge skylines with the same y value
fn merge(&mut self) {
let mut i = 1;
while i < self.skylines.len() {
if self.skylines[i - 1].y == self.skylines[i].y {
self.skylines[i - 1].width += self.skylines[i].width;
self.skylines.remove(i);
} else {
i += 1;
}
}
}
//pub fn pack(&mut self, )
}
impl AtlasPacker for SkylinePacker {
fn new(size: UVec2) -> Self {
SkylinePacker::new(size)
}
fn frame(&self, id: usize) -> Option<AtlasFrame> {
self.frames.get(&id).cloned()
}
fn frames(&self) -> &HashMap<usize, AtlasFrame> {
&self.frames
}
fn pack(&mut self, width: u32, height: u32) -> Result<usize, AtlasPackError> {
if let Some((i, frame)) = self.find_skyline(width, height) {
self.split(i, &frame);
self.merge();
let frame_idx = self.frame_idx;
self.frame_idx += 1;
self.frames.insert(frame_idx, frame);
Ok(frame_idx)
} else {
Err(AtlasPackError::NotEnoughSpace)
}
}
}