Implement Shadows #24
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,142 @@
|
|||
{
|
||||
"asset":{
|
||||
"generator":"Khronos glTF Blender I/O v4.1.63",
|
||||
"version":"2.0"
|
||||
},
|
||||
"scene":0,
|
||||
"scenes":[
|
||||
{
|
||||
"name":"Scene",
|
||||
"nodes":[
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes":[
|
||||
{
|
||||
"mesh":0,
|
||||
"name":"Cube",
|
||||
"scale":[
|
||||
10,
|
||||
0.25,
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"materials":[
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Material.001",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorTexture":{
|
||||
"index":0
|
||||
},
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes":[
|
||||
{
|
||||
"name":"Cube.001",
|
||||
"primitives":[
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":0,
|
||||
"NORMAL":1,
|
||||
"TEXCOORD_0":2
|
||||
},
|
||||
"indices":3,
|
||||
"material":0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures":[
|
||||
{
|
||||
"sampler":0,
|
||||
"source":0
|
||||
}
|
||||
],
|
||||
"images":[
|
||||
{
|
||||
"mimeType":"image/jpeg",
|
||||
"name":"wood1",
|
||||
"uri":"wood1.jpg"
|
||||
}
|
||||
],
|
||||
"accessors":[
|
||||
{
|
||||
"bufferView":0,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"max":[
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"min":[
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":1,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":2,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":3,
|
||||
"componentType":5123,
|
||||
"count":36,
|
||||
"type":"SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews":[
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":288,
|
||||
"byteOffset":0,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":288,
|
||||
"byteOffset":288,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":192,
|
||||
"byteOffset":576,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":72,
|
||||
"byteOffset":768,
|
||||
"target":34963
|
||||
}
|
||||
],
|
||||
"samplers":[
|
||||
{
|
||||
"magFilter":9729,
|
||||
"minFilter":9987
|
||||
}
|
||||
],
|
||||
"buffers":[
|
||||
{
|
||||
"byteLength":840,
|
||||
"uri":"model.bin"
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
|
@ -128,15 +128,29 @@ fn setup_scene_plugin(game: &mut Game) {
|
|||
platform_gltf.wait_recurse_dependencies_load();
|
||||
let platform_mesh = &platform_gltf.data_ref().unwrap().scenes[0];
|
||||
|
||||
let palm_tree_platform_gltf = resman
|
||||
.request::<Gltf>("../assets/shadows-platform-palmtree.glb")
|
||||
.unwrap();
|
||||
|
||||
palm_tree_platform_gltf.wait_recurse_dependencies_load();
|
||||
let palm_tree_platform_mesh = &palm_tree_platform_gltf.data_ref().unwrap().scenes[0];
|
||||
|
||||
drop(resman);
|
||||
|
||||
// cube in the air
|
||||
world.spawn((
|
||||
/* world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -2.0, -5.0),
|
||||
));
|
||||
|
||||
// cube really high in the air
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(-6.0, 0.0, -5.0),
|
||||
));
|
||||
|
||||
// cube on the right, on the ground
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
|
@ -149,10 +163,19 @@ fn setup_scene_plugin(game: &mut Game) {
|
|||
WorldTransform::default(),
|
||||
//Transform::from_xyz(0.0, -5.0, -5.0),
|
||||
Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
|
||||
)); */
|
||||
|
||||
world.spawn((
|
||||
palm_tree_platform_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(5.0, -15.0, 0.0),
|
||||
//Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
|
||||
));
|
||||
|
||||
//shadows-platform-palmtree.glb
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(0.0, 2.5, 0.0);
|
||||
let mut light_tran = Transform::from_xyz(0.0, 0.0, 0.0);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||
light_tran.rotate_y(math::Angle::Degrees(-35.0));
|
||||
|
@ -192,7 +215,9 @@ fn setup_scene_plugin(game: &mut Game) {
|
|||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation += math::Vec3::new(0.0, 2.0, 10.5);
|
||||
camera.transform.rotate_x(math::Angle::Degrees(-17.0));
|
||||
//camera.transform.translation += math::Vec3::new(0.0, 2.0, 10.5);
|
||||
camera.transform.translation = math::Vec3::new(-3.0, -8.0, -3.0);
|
||||
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
|
||||
camera.transform.rotate_y(math::Angle::Degrees(-55.0));
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
|
@ -63,6 +63,14 @@ pub enum SlotValue {
|
|||
}
|
||||
|
||||
impl SlotValue {
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub fn is_lazy(&self) -> bool {
|
||||
matches!(self, Self::Lazy)
|
||||
}
|
||||
|
||||
pub fn as_texture_view(&self) -> Option<&Arc<wgpu::TextureView>> {
|
||||
bind_match!(self, Self::TextureView(v) => v)
|
||||
}
|
||||
|
|
|
@ -115,6 +115,11 @@ impl Node for MeshPass {
|
|||
.expect("missing ShadowMapsPassSlots::ShadowAtlasSampler")
|
||||
.as_sampler()
|
||||
.unwrap();
|
||||
let atlas_sampler_compare = graph
|
||||
.slot_value(ShadowMapsPassSlots::ShadowAtlasSamplerComparison)
|
||||
.expect("missing ShadowMapsPassSlots::ShadowAtlasSamplerComparison")
|
||||
.as_sampler()
|
||||
.unwrap();
|
||||
let shadow_settings_buf = graph
|
||||
.slot_value(ShadowMapsPassSlots::ShadowSettingsUniform)
|
||||
.expect("missing ShadowMapsPassSlots::ShadowSettingsUniform")
|
||||
|
@ -130,6 +135,11 @@ impl Node for MeshPass {
|
|||
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer")
|
||||
.as_buffer()
|
||||
.unwrap();
|
||||
let pcss_poisson_disc = graph
|
||||
.slot_value(ShadowMapsPassSlots::PcssPoissonDiscBuffer)
|
||||
.expect("missing ShadowMapsPassSlots::PcssPoissonDiscBuffer")
|
||||
.as_buffer()
|
||||
.unwrap();
|
||||
|
||||
let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("bgl_shadows_atlas"),
|
||||
|
@ -147,11 +157,17 @@ impl Node for MeshPass {
|
|||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
|
@ -161,7 +177,7 @@ impl Node for MeshPass {
|
|||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
|
@ -171,7 +187,17 @@ impl Node for MeshPass {
|
|||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
binding: 5,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 6,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
|
@ -197,6 +223,10 @@ impl Node for MeshPass {
|
|||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(atlas_sampler_compare),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: shadow_settings_buf,
|
||||
offset: 0,
|
||||
|
@ -204,7 +234,7 @@ impl Node for MeshPass {
|
|||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
binding: 4,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: light_uniform_buf,
|
||||
offset: 0,
|
||||
|
@ -212,13 +242,21 @@ impl Node for MeshPass {
|
|||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 4,
|
||||
binding: 5,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: pcf_poisson_disc,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 6,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: pcss_poisson_disc,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::{
|
|||
};
|
||||
|
||||
use fast_poisson::Poisson2D;
|
||||
use glam::Vec2;
|
||||
use itertools::Itertools;
|
||||
use lyra_ecs::{
|
||||
query::{filter::Has, Entities},
|
||||
|
@ -25,18 +26,19 @@ use crate::render::{
|
|||
|
||||
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
||||
|
||||
const PCF_SAMPLES_NUM: u32 = 6;
|
||||
const SHADOW_SIZE: glam::UVec2 = glam::uvec2(1024, 1024);
|
||||
const SHADOW_SIZE: glam::UVec2 = glam::uvec2(4096, 4096);
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
||||
pub enum ShadowMapsPassSlots {
|
||||
ShadowAtlasTexture,
|
||||
ShadowAtlasTextureView,
|
||||
ShadowAtlasSampler,
|
||||
ShadowAtlasSamplerComparison,
|
||||
ShadowAtlasSizeBuffer,
|
||||
ShadowLightUniformsBuffer,
|
||||
ShadowSettingsUniform,
|
||||
PcfPoissonDiscBuffer,
|
||||
PcssPoissonDiscBuffer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
||||
|
@ -74,6 +76,7 @@ pub struct ShadowMapsPass {
|
|||
atlas: LightShadowMapAtlas,
|
||||
/// The depth map atlas sampler
|
||||
atlas_sampler: Rc<wgpu::Sampler>,
|
||||
atlas_sampler_compare: Rc<wgpu::Sampler>,
|
||||
}
|
||||
|
||||
impl ShadowMapsPass {
|
||||
|
@ -98,7 +101,7 @@ impl ShadowMapsPass {
|
|||
device,
|
||||
wgpu::TextureFormat::Depth32Float,
|
||||
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
SHADOW_SIZE * 8,
|
||||
SHADOW_SIZE * 2,
|
||||
);
|
||||
|
||||
let atlas_size_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
|
@ -107,16 +110,28 @@ impl ShadowMapsPass {
|
|||
contents: bytemuck::bytes_of(&atlas.atlas_size()),
|
||||
});
|
||||
|
||||
let sampler_compare = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("compare_sampler_shadow_map_atlas"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToBorder,
|
||||
address_mode_v: wgpu::AddressMode::ClampToBorder,
|
||||
address_mode_w: wgpu::AddressMode::ClampToBorder,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
border_color: Some(wgpu::SamplerBorderColor::OpaqueWhite),
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("sampler_shadow_map_atlas"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToBorder,
|
||||
address_mode_v: wgpu::AddressMode::ClampToBorder,
|
||||
address_mode_w: wgpu::AddressMode::ClampToBorder,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
border_color: Some(wgpu::SamplerBorderColor::OpaqueWhite),
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
@ -155,6 +170,7 @@ impl ShadowMapsPass {
|
|||
point_light_pipeline: None,
|
||||
|
||||
atlas_sampler: Rc::new(sampler),
|
||||
atlas_sampler_compare: Rc::new(sampler_compare),
|
||||
atlas: LightShadowMapAtlas(Arc::new(RwLock::new(atlas))),
|
||||
}
|
||||
}
|
||||
|
@ -183,6 +199,14 @@ impl ShadowMapsPass {
|
|||
|
||||
let projection =
|
||||
glam::Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, NEAR_PLANE, FAR_PLANE);
|
||||
|
||||
// honestly no clue why this works, but I got it from here and the results are good
|
||||
// https://github.com/asylum2010/Asylum_Tutorials/blob/423e5edfaee7b5ea450a450e65f2eabf641b2482/ShaderTutors/43_ShadowMapFiltering/main.cpp#L323
|
||||
let frustum_size = Vec2::new(0.5 * projection.col(0).x, 0.5 * projection.col(1).y);
|
||||
// maybe its better to make this a vec2 on the gpu?
|
||||
let size_avg = (frustum_size.x + frustum_size.y) / 2.0;
|
||||
let light_size_uv = 0.2 * size_avg;
|
||||
|
||||
let look_view = glam::Mat4::look_to_rh(
|
||||
light_pos.translation,
|
||||
light_pos.forward(),
|
||||
|
@ -196,7 +220,8 @@ impl ShadowMapsPass {
|
|||
atlas_frame,
|
||||
near_plane: NEAR_PLANE,
|
||||
far_plane,
|
||||
_padding1: [0; 2],
|
||||
light_size_uv,
|
||||
_padding1: 0,
|
||||
light_pos: light_pos.translation,
|
||||
_padding2: 0,
|
||||
};
|
||||
|
@ -284,7 +309,8 @@ impl ShadowMapsPass {
|
|||
atlas_frame: frames[i],
|
||||
near_plane: NEAR_PLANE,
|
||||
far_plane,
|
||||
_padding1: [0; 2],
|
||||
light_size_uv: 0.0,
|
||||
_padding1: 0,
|
||||
light_pos: light_trans,
|
||||
_padding2: 0,
|
||||
},
|
||||
|
@ -322,26 +348,29 @@ impl ShadowMapsPass {
|
|||
fn create_poisson_disc_buffer(&self, device: &wgpu::Device, label: &str, num_samples: u32) -> wgpu::Buffer {
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size: mem::size_of::<glam::Vec2>() as u64 * (num_samples.pow(2)) as u64,
|
||||
size: mem::size_of::<glam::Vec2>() as u64 * (num_samples * 2) as u64,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate and write a Poisson disc to `buffer` with `num_pcf_samples.pow(2)` amount of points.
|
||||
fn write_poisson_disc(&self, queue: &wgpu::Queue, buffer: &wgpu::Buffer, num_pcf_samples: u32) {
|
||||
let num_points = num_pcf_samples.pow(2);
|
||||
let num_floats = num_points * 2; // points are vec2f
|
||||
fn write_poisson_disc(&self, queue: &wgpu::Queue, buffer: &wgpu::Buffer, num_samples: u32) {
|
||||
//let num_points = num_samples.pow(2);
|
||||
let num_floats = num_samples * 2; // points are vec2f
|
||||
let min_dist = (num_floats as f32).sqrt() / num_floats as f32;
|
||||
//let min_dist = (num_samples as f32).sqrt() / num_samples as f32;
|
||||
let mut points = vec![];
|
||||
|
||||
// use a while loop to ensure that the correct number of floats is created
|
||||
while points.len() < num_floats as usize {
|
||||
let poisson = Poisson2D::new()
|
||||
.with_dimensions([1.0, 1.0], min_dist)
|
||||
.with_samples(num_pcf_samples);
|
||||
.with_samples(num_samples);
|
||||
|
||||
points = poisson.iter().flatten().collect_vec();
|
||||
points = poisson.iter().flatten()
|
||||
.map(|p| p * 2.0 - 1.0)
|
||||
.collect_vec();
|
||||
|
||||
}
|
||||
points.truncate(num_floats as _);
|
||||
|
@ -377,6 +406,12 @@ impl Node for ShadowMapsPass {
|
|||
Some(SlotValue::Sampler(self.atlas_sampler.clone())),
|
||||
);
|
||||
|
||||
node.add_sampler_slot(
|
||||
ShadowMapsPassSlots::ShadowAtlasSamplerComparison,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Sampler(self.atlas_sampler_compare.clone())),
|
||||
);
|
||||
|
||||
node.add_buffer_slot(
|
||||
ShadowMapsPassSlots::ShadowLightUniformsBuffer,
|
||||
SlotAttribute::Output,
|
||||
|
@ -404,11 +439,20 @@ impl Node for ShadowMapsPass {
|
|||
Some(SlotValue::Buffer(Arc::new(settings_buffer))),
|
||||
);
|
||||
|
||||
let def_settings = ShadowSettings::default();
|
||||
node.add_buffer_slot(
|
||||
ShadowMapsPassSlots::PcfPoissonDiscBuffer,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Arc::new(
|
||||
self.create_poisson_disc_buffer(device, "buffer_poisson_disc_pcf", PCF_SAMPLES_NUM),
|
||||
self.create_poisson_disc_buffer(device, "buffer_poisson_disc_pcf", def_settings.pcf_samples_num),
|
||||
))),
|
||||
);
|
||||
|
||||
node.add_buffer_slot(
|
||||
ShadowMapsPassSlots::PcssPoissonDiscBuffer,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Arc::new(
|
||||
self.create_poisson_disc_buffer(device, "buffer_poisson_disc_pcss", def_settings.pcss_blocker_search_samples),
|
||||
))),
|
||||
);
|
||||
|
||||
|
@ -424,9 +468,14 @@ impl Node for ShadowMapsPass {
|
|||
{
|
||||
// TODO: Update the poisson disc every time the PCF sampling point number changed
|
||||
if !world.has_resource::<ShadowSettings>() {
|
||||
let def_settings = ShadowSettings::default();
|
||||
let buffer = graph.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
|
||||
.unwrap().as_buffer().unwrap();
|
||||
self.write_poisson_disc(&context.queue, &buffer, ShadowSettings::default().pcf_samples_num);
|
||||
self.write_poisson_disc(&context.queue, &buffer, def_settings.pcf_samples_num);
|
||||
|
||||
let buffer = graph.slot_value(ShadowMapsPassSlots::PcssPoissonDiscBuffer)
|
||||
.unwrap().as_buffer().unwrap();
|
||||
self.write_poisson_disc(&context.queue, &buffer, def_settings.pcss_blocker_search_samples);
|
||||
}
|
||||
|
||||
// TODO: only write buffer on changes to resource
|
||||
|
@ -717,7 +766,9 @@ pub struct LightShadowUniform {
|
|||
atlas_frame: AtlasFrame, // 2xUVec2 (4xf32), so no padding needed
|
||||
near_plane: f32,
|
||||
far_plane: f32,
|
||||
_padding1: [u32; 2],
|
||||
/// Light size in UV space (light_size / frustum_size)
|
||||
light_size_uv: f32,
|
||||
_padding1: u32,
|
||||
light_pos: glam::Vec3,
|
||||
_padding2: u32,
|
||||
}
|
||||
|
@ -758,13 +809,22 @@ impl LightShadowMapAtlas {
|
|||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ShadowSettings {
|
||||
/// How many PCF filtering samples are used per dimension.
|
||||
///
|
||||
/// A value of 16 is common.
|
||||
pub pcf_samples_num: u32,
|
||||
/// How many samples are used for the PCSS blocker search step.
|
||||
///
|
||||
/// Multiple samples are required to avoid holes int he penumbra due to missing blockers.
|
||||
/// A value of 16 is common.
|
||||
pub pcss_blocker_search_samples: u32,
|
||||
}
|
||||
|
||||
impl Default for ShadowSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pcf_samples_num: PCF_SAMPLES_NUM,
|
||||
pcf_samples_num: 64,
|
||||
pcss_blocker_search_samples: 36,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -774,12 +834,14 @@ impl Default for ShadowSettings {
|
|||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct ShadowSettingsUniform {
|
||||
pcf_samples_num: u32,
|
||||
pcss_blocker_search_samples: u32,
|
||||
}
|
||||
|
||||
impl From<ShadowSettings> for ShadowSettingsUniform {
|
||||
fn from(value: ShadowSettings) -> Self {
|
||||
Self {
|
||||
pcf_samples_num: value.pcf_samples_num,
|
||||
pcss_blocker_search_samples: value.pcss_blocker_search_samples,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,11 +117,13 @@ struct LightShadowMapUniform {
|
|||
atlas_frame: TextureAtlasFrame,
|
||||
near_plane: f32,
|
||||
far_plane: f32,
|
||||
light_size_uv: f32,
|
||||
light_pos: vec3<f32>,
|
||||
}
|
||||
|
||||
struct ShadowSettingsUniform {
|
||||
pcf_samples_num: u32,
|
||||
pcss_blocker_search_samples: u32,
|
||||
}
|
||||
|
||||
@group(4) @binding(0)
|
||||
|
@ -132,13 +134,17 @@ var t_light_grid: texture_storage_2d<rg32uint, read_write>; // rg32uint = vec2<u
|
|||
@group(5) @binding(0)
|
||||
var t_shadow_maps_atlas: texture_depth_2d;
|
||||
@group(5) @binding(1)
|
||||
var s_shadow_maps_atlas: sampler_comparison;
|
||||
var s_shadow_maps_atlas: sampler;
|
||||
@group(5) @binding(2)
|
||||
var<uniform> u_shadow_settings: ShadowSettingsUniform;
|
||||
var s_shadow_maps_atlas_compare: sampler_comparison;
|
||||
@group(5) @binding(3)
|
||||
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
|
||||
var<uniform> u_shadow_settings: ShadowSettingsUniform;
|
||||
@group(5) @binding(4)
|
||||
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
|
||||
@group(5) @binding(5)
|
||||
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
|
||||
@group(5) @binding(6)
|
||||
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
|
@ -185,8 +191,12 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
return vec4<f32>(light_object_res, object_color.a);
|
||||
}
|
||||
|
||||
/// Get the cube map side index of a 3d texture coord
|
||||
/// Convert 3d coords for an unwrapped cubemap to 2d coords and a side index of the cube map.
|
||||
///
|
||||
/// The `xy` components are the 2d coordinates in the side of the cube, and `z` is the cube
|
||||
/// map side index.
|
||||
///
|
||||
/// Cube map index results:
|
||||
/// 0 -> UNKNOWN
|
||||
/// 1 -> right
|
||||
/// 2 -> left
|
||||
|
@ -194,7 +204,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
/// 4 -> bottom
|
||||
/// 5 -> near
|
||||
/// 6 -> far
|
||||
fn get_side_idx(tex_coord: vec3<f32>) -> vec3<f32> {
|
||||
fn coords_to_cube_atlas(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);
|
||||
|
@ -234,14 +244,7 @@ fn get_side_idx(tex_coord: vec3<f32>) -> vec3<f32> {
|
|||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
@ -264,10 +267,11 @@ fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light
|
|||
);
|
||||
|
||||
// use a bias to avoid shadow acne
|
||||
let bias = max(0.05 * (1.0 - dot(normal, light_dir)), 0.005);
|
||||
let bias = 0.005;//max(0.05 * (1.0 - dot(normal, light_dir)), 0.005);
|
||||
let current_depth = proj_coords.z - bias;
|
||||
|
||||
var shadow = pcf_dir_light(region_coords, current_depth, shadow_u);
|
||||
//var shadow = pcf_dir_light(region_coords, current_depth, shadow_u, 1.0);
|
||||
var shadow = pcss_dir_light(xy_remapped, current_depth, shadow_u);
|
||||
|
||||
// dont cast shadows outside the light's far plane
|
||||
if (proj_coords.z > 1.0) {
|
||||
|
@ -282,35 +286,87 @@ fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light
|
|||
return shadow;
|
||||
}
|
||||
|
||||
/// Calculate the shadow coefficient using PCF of a directional light
|
||||
fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform) -> f32 {
|
||||
let half_filter_size = f32(u_shadow_settings.pcf_samples_num) / 2.0;
|
||||
let texel_size = 1.0 / vec2<f32>(f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height));
|
||||
// Comes from https://developer.download.nvidia.com/whitepapers/2008/PCSS_Integration.pdf
|
||||
fn search_width(light_near: f32, uv_light_size: f32, receiver_depth: f32) -> f32 {
|
||||
return uv_light_size * (receiver_depth - light_near) / receiver_depth;
|
||||
}
|
||||
|
||||
// Sample PCF
|
||||
var shadow = 0.0;
|
||||
var i = 0;
|
||||
for (var x = -half_filter_size; x <= half_filter_size; x += 1.0) {
|
||||
for (var y = -half_filter_size; y <= half_filter_size; y += 1.0) {
|
||||
//let random = u_pcf_poisson_disc[i] * texel_size;
|
||||
let offset = tex_coords + (u_pcf_poisson_disc[i] + vec2<f32>(x, y)) * texel_size;
|
||||
|
||||
let pcf_depth = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas, offset, test_depth);
|
||||
shadow += pcf_depth;
|
||||
/// Convert texture coords to be texture coords of an atlas frame.
|
||||
fn to_atlas_frame_coords(shadow_u: LightShadowMapUniform, coords: vec2<f32>) -> vec2<f32> {
|
||||
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
|
||||
|
||||
// get the rect of the frame as a vec4
|
||||
var region_rect = vec4<f32>(f32(shadow_u.atlas_frame.x), f32(shadow_u.atlas_frame.y), f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height));
|
||||
// put the frame rect in atlas UV space
|
||||
region_rect /= f32(atlas_dimensions.x);
|
||||
|
||||
// lerp input coords
|
||||
let region_coords = vec2<f32>(
|
||||
mix(region_rect.x, region_rect.x + region_rect.z, coords.x),
|
||||
mix(region_rect.y, region_rect.y + region_rect.w, coords.y)
|
||||
);
|
||||
|
||||
return region_coords;
|
||||
}
|
||||
|
||||
i++;
|
||||
/// Find the average blocker distance for a directiona llight
|
||||
fn find_blocker_distance_dir_light(tex_coords: vec2<f32>, receiver_depth: f32, bias: f32, shadow_u: LightShadowMapUniform) -> vec2<f32> {
|
||||
let search_width = search_width(shadow_u.near_plane, shadow_u.light_size_uv, receiver_depth);
|
||||
|
||||
var blockers = 0;
|
||||
var avg_dist = 0.0;
|
||||
let samples = i32(u_shadow_settings.pcss_blocker_search_samples);
|
||||
for (var i = 0; i < samples; i++) {
|
||||
let offset_coords = tex_coords + u_pcss_poisson_disc[i] * search_width;
|
||||
let new_coords = to_atlas_frame_coords(shadow_u, offset_coords);
|
||||
let z = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, new_coords, 0.0);
|
||||
|
||||
if z < (receiver_depth - bias) {
|
||||
blockers += 1;
|
||||
avg_dist += z;
|
||||
}
|
||||
}
|
||||
shadow /= pow(f32(u_shadow_settings.pcf_samples_num), 2.0);
|
||||
// ensure the shadow value does not go above 1.0
|
||||
shadow = min(shadow, 1.0);
|
||||
|
||||
return shadow;
|
||||
let b = f32(blockers);
|
||||
return vec2<f32>(avg_dist / b, b);
|
||||
}
|
||||
|
||||
fn pcss_dir_light(tex_coords: vec2<f32>, receiver_depth: f32, shadow_u: LightShadowMapUniform) -> f32 {
|
||||
let blocker_search = find_blocker_distance_dir_light(tex_coords, receiver_depth, 0.0, shadow_u);
|
||||
|
||||
// If no blockers were found, exit now to save in filtering
|
||||
if blocker_search.y == 0.0 {
|
||||
return 1.0;
|
||||
}
|
||||
let blocker_depth = blocker_search.x;
|
||||
|
||||
// penumbra estimation
|
||||
let penumbra_width = (receiver_depth - blocker_depth) / blocker_depth;
|
||||
|
||||
// PCF
|
||||
let uv_radius = penumbra_width * shadow_u.light_size_uv * shadow_u.near_plane / receiver_depth;
|
||||
return pcf_dir_light(tex_coords, receiver_depth, shadow_u, uv_radius);
|
||||
}
|
||||
|
||||
/// Calculate the shadow coefficient using PCF of a directional light
|
||||
fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, uv_radius: f32) -> f32 {
|
||||
var shadow = 0.0;
|
||||
let samples_num = i32(u_shadow_settings.pcf_samples_num);
|
||||
for (var i = 0; i < samples_num; i++) {
|
||||
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
|
||||
let new_coords = to_atlas_frame_coords(shadow_u, offset);
|
||||
|
||||
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
|
||||
}
|
||||
shadow /= f32(samples_num);
|
||||
|
||||
// clamp shadow to [0; 1]
|
||||
return saturate(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));
|
||||
let temp = coords_to_cube_atlas(normalize(frag_to_light));
|
||||
var coords_2d = temp.xy;
|
||||
let cube_idx = i32(temp.z);
|
||||
|
||||
|
@ -342,7 +398,7 @@ fn calc_shadow_point(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: v
|
|||
var current_depth = length(frag_to_light) - bias;
|
||||
current_depth /= u.far_plane;
|
||||
|
||||
var shadow = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas, coords_2d, current_depth);
|
||||
var shadow = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, coords_2d, current_depth);
|
||||
|
||||
return shadow;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue