render: implement PCF for point lights, support per-light shadow settings
This commit is contained in:
parent
c91ee67961
commit
fef709d5f1
|
@ -5,8 +5,11 @@ use lyra_engine::{
|
||||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||||
},
|
},
|
||||||
math::{self, Transform, Vec3},
|
math::{self, Quat, Transform, Vec3},
|
||||||
render::light::directional::DirectionalLight,
|
render::{
|
||||||
|
graph::{ShadowCasterSettings, ShadowFilteringMode},
|
||||||
|
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,
|
||||||
|
@ -189,19 +192,27 @@ fn setup_scene_plugin(game: &mut Game) {
|
||||||
light_tran,
|
light_tran,
|
||||||
));
|
));
|
||||||
|
|
||||||
/* world.spawn((
|
world.spawn((
|
||||||
//cube_mesh.clone(),
|
cube_mesh.clone(),
|
||||||
PointLight {
|
PointLight {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
color: Vec3::new(0.133, 0.098, 0.91),
|
color: Vec3::new(0.133, 0.098, 0.91),
|
||||||
intensity: 2.0,
|
intensity: 2.0,
|
||||||
range: 9.0,
|
range: 10.0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Transform::from_xyz(5.0, -2.5, -3.3),
|
ShadowCasterSettings {
|
||||||
|
filtering_mode: ShadowFilteringMode::Pcf,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Transform::new(
|
||||||
|
Vec3::new(4.0 - 1.43, -13.0, 1.53),
|
||||||
|
Quat::IDENTITY,
|
||||||
|
Vec3::new(0.5, 0.5, 0.5),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
world.spawn((
|
/* world.spawn((
|
||||||
//cube_mesh.clone(),
|
//cube_mesh.clone(),
|
||||||
PointLight {
|
PointLight {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -216,8 +227,14 @@ fn setup_scene_plugin(game: &mut Game) {
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_3d();
|
let mut camera = CameraComponent::new_3d();
|
||||||
//camera.transform.translation += math::Vec3::new(0.0, 2.0, 10.5);
|
//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.translation = math::Vec3::new(-3.0, -8.0, -3.0);
|
||||||
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
|
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
|
||||||
camera.transform.rotate_y(math::Angle::Degrees(-55.0));
|
camera.transform.rotate_y(math::Angle::Degrees(-55.0)); */
|
||||||
|
|
||||||
|
camera.transform.translation = math::Vec3::new(15.0, -8.0, 1.0);
|
||||||
|
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
|
||||||
|
//camera.transform.rotate_y(math::Angle::Degrees(-90.0));
|
||||||
|
camera.transform.rotate_y(math::Angle::Degrees(90.0));
|
||||||
|
|
||||||
world.spawn((camera, FreeFlyCamera::default()));
|
world.spawn((camera, FreeFlyCamera::default()));
|
||||||
}
|
}
|
|
@ -135,6 +135,11 @@ impl Node for MeshPass {
|
||||||
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer")
|
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer")
|
||||||
.as_buffer()
|
.as_buffer()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let pcf_poisson_disc_3d = graph
|
||||||
|
.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer3d)
|
||||||
|
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer3d")
|
||||||
|
.as_buffer()
|
||||||
|
.unwrap();
|
||||||
let pcss_poisson_disc = graph
|
let pcss_poisson_disc = graph
|
||||||
.slot_value(ShadowMapsPassSlots::PcssPoissonDiscBuffer)
|
.slot_value(ShadowMapsPassSlots::PcssPoissonDiscBuffer)
|
||||||
.expect("missing ShadowMapsPassSlots::PcssPoissonDiscBuffer")
|
.expect("missing ShadowMapsPassSlots::PcssPoissonDiscBuffer")
|
||||||
|
@ -206,6 +211,16 @@ impl Node for MeshPass {
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 7,
|
||||||
|
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,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -251,6 +266,14 @@ impl Node for MeshPass {
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 6,
|
binding: 6,
|
||||||
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
|
buffer: pcf_poisson_disc_3d,
|
||||||
|
offset: 0,
|
||||||
|
size: None,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 7,
|
||||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
buffer: pcss_poisson_disc,
|
buffer: pcss_poisson_disc,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque, mem, rc::Rc, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}
|
collections::VecDeque,
|
||||||
|
mem,
|
||||||
|
rc::Rc,
|
||||||
|
sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use fast_poisson::Poisson2D;
|
use fast_poisson::{Poisson2D, Poisson3D};
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lyra_ecs::{
|
use lyra_ecs::{
|
||||||
|
@ -26,7 +29,7 @@ use crate::render::{
|
||||||
|
|
||||||
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
||||||
|
|
||||||
const SHADOW_SIZE: glam::UVec2 = glam::uvec2(4096, 4096);
|
const SHADOW_SIZE: glam::UVec2 = glam::uvec2(1024, 1024);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
||||||
pub enum ShadowMapsPassSlots {
|
pub enum ShadowMapsPassSlots {
|
||||||
|
@ -38,6 +41,7 @@ pub enum ShadowMapsPassSlots {
|
||||||
ShadowLightUniformsBuffer,
|
ShadowLightUniformsBuffer,
|
||||||
ShadowSettingsUniform,
|
ShadowSettingsUniform,
|
||||||
PcfPoissonDiscBuffer,
|
PcfPoissonDiscBuffer,
|
||||||
|
PcfPoissonDiscBuffer3d,
|
||||||
PcssPoissonDiscBuffer,
|
PcssPoissonDiscBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +105,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 * 2,
|
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 {
|
||||||
|
@ -182,23 +186,49 @@ impl ShadowMapsPass {
|
||||||
light_type: LightType,
|
light_type: LightType,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
light_pos: Transform,
|
light_pos: Transform,
|
||||||
far_plane: f32,
|
are_settings_custom: bool,
|
||||||
|
shadow_settings: ShadowCasterSettings,
|
||||||
) -> LightDepthMap {
|
) -> LightDepthMap {
|
||||||
const NEAR_PLANE: f32 = 0.1;
|
|
||||||
const FAR_PLANE: f32 = 45.0;
|
|
||||||
|
|
||||||
let mut atlas = self.atlas.get_mut();
|
let mut atlas = self.atlas.get_mut();
|
||||||
|
|
||||||
|
let u = ShadowSettingsUniform::new(
|
||||||
|
shadow_settings.filtering_mode,
|
||||||
|
shadow_settings.pcf_samples_num,
|
||||||
|
shadow_settings.pcss_blocker_search_samples,
|
||||||
|
);
|
||||||
|
|
||||||
|
let has_shadow_settings = if are_settings_custom {
|
||||||
|
1
|
||||||
|
} else { 0 };
|
||||||
|
/* let (has_shadow_settings, pcf_samples_num, pcss_samples_num) = if are_settings_custom {
|
||||||
|
|
||||||
|
(1, u.pcf_samples_num, u.pcss_blocker_search_samples)
|
||||||
|
} else {
|
||||||
|
(0, , 0)
|
||||||
|
}; */
|
||||||
|
|
||||||
|
/* shadow_settings.map(|ss| {
|
||||||
|
let u = ShadowSettingsUniform::new(ss.filtering_mode, ss.pcf_samples_num, ss.pcss_blocker_search_samples);
|
||||||
|
(1, u.pcf_samples_num, u.pcss_blocker_search_samples)
|
||||||
|
}).unwrap_or((0, 0, 0)); */
|
||||||
|
|
||||||
let (start_atlas_idx, uniform_indices) = match light_type {
|
let (start_atlas_idx, uniform_indices) = match light_type {
|
||||||
LightType::Directional => {
|
LightType::Directional => {
|
||||||
|
let directional_size = SHADOW_SIZE * 4;
|
||||||
// directional lights require a single map, so allocate that in the atlas.
|
// directional lights require a single map, so allocate that in the atlas.
|
||||||
let atlas_index = atlas
|
let atlas_index = atlas
|
||||||
.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
|
.pack(directional_size.x as _, directional_size.y as _)
|
||||||
.expect("failed to pack new shadow map into texture atlas");
|
.expect("failed to pack new shadow map into texture atlas");
|
||||||
let atlas_frame = atlas.texture_frame(atlas_index).expect("Frame missing");
|
let atlas_frame = atlas.texture_frame(atlas_index).expect("Frame missing");
|
||||||
|
|
||||||
let projection =
|
let projection = glam::Mat4::orthographic_rh(
|
||||||
glam::Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, NEAR_PLANE, FAR_PLANE);
|
-10.0,
|
||||||
|
10.0,
|
||||||
|
-10.0,
|
||||||
|
10.0,
|
||||||
|
shadow_settings.near_plane,
|
||||||
|
shadow_settings.far_plane,
|
||||||
|
);
|
||||||
|
|
||||||
// honestly no clue why this works, but I got it from here and the results are good
|
// 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
|
// https://github.com/asylum2010/Asylum_Tutorials/blob/423e5edfaee7b5ea450a450e65f2eabf641b2482/ShaderTutors/43_ShadowMapFiltering/main.cpp#L323
|
||||||
|
@ -218,12 +248,15 @@ impl ShadowMapsPass {
|
||||||
let u = LightShadowUniform {
|
let u = LightShadowUniform {
|
||||||
space_mat: light_proj,
|
space_mat: light_proj,
|
||||||
atlas_frame,
|
atlas_frame,
|
||||||
near_plane: NEAR_PLANE,
|
near_plane: shadow_settings.near_plane,
|
||||||
far_plane,
|
far_plane: shadow_settings.far_plane,
|
||||||
light_size_uv,
|
light_size_uv,
|
||||||
_padding1: 0,
|
_padding1: 0,
|
||||||
light_pos: light_pos.translation,
|
light_pos: light_pos.translation,
|
||||||
_padding2: 0,
|
has_shadow_settings,
|
||||||
|
pcf_samples_num: u.pcf_samples_num,
|
||||||
|
pcss_blocker_search_samples: u.pcss_blocker_search_samples,
|
||||||
|
_padding2: [0; 2],
|
||||||
};
|
};
|
||||||
|
|
||||||
let uniform_index = self.light_uniforms_buffer.insert(queue, &u);
|
let uniform_index = self.light_uniforms_buffer.insert(queue, &u);
|
||||||
|
@ -237,8 +270,8 @@ impl ShadowMapsPass {
|
||||||
let projection = glam::Mat4::perspective_rh(
|
let projection = glam::Mat4::perspective_rh(
|
||||||
Angle::Degrees(90.0).to_radians(),
|
Angle::Degrees(90.0).to_radians(),
|
||||||
aspect,
|
aspect,
|
||||||
NEAR_PLANE,
|
shadow_settings.near_plane,
|
||||||
far_plane,
|
shadow_settings.far_plane,
|
||||||
);
|
);
|
||||||
|
|
||||||
let light_trans = light_pos.translation;
|
let light_trans = light_pos.translation;
|
||||||
|
@ -307,12 +340,15 @@ impl ShadowMapsPass {
|
||||||
&LightShadowUniform {
|
&LightShadowUniform {
|
||||||
space_mat: views[i],
|
space_mat: views[i],
|
||||||
atlas_frame: frames[i],
|
atlas_frame: frames[i],
|
||||||
near_plane: NEAR_PLANE,
|
near_plane: shadow_settings.near_plane,
|
||||||
far_plane,
|
far_plane: shadow_settings.far_plane,
|
||||||
light_size_uv: 0.0,
|
light_size_uv: 0.0,
|
||||||
_padding1: 0,
|
_padding1: 0,
|
||||||
light_pos: light_trans,
|
light_pos: light_trans,
|
||||||
_padding2: 0,
|
has_shadow_settings,
|
||||||
|
pcf_samples_num: u.pcf_samples_num,
|
||||||
|
pcss_blocker_search_samples: u.pcss_blocker_search_samples,
|
||||||
|
_padding2: [0; 2],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
indices[i] = uniform_i;
|
indices[i] = uniform_i;
|
||||||
|
@ -345,31 +381,66 @@ impl ShadowMapsPass {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the gpu buffer for a poisson disc
|
/// Create the gpu buffer for a poisson disc
|
||||||
fn create_poisson_disc_buffer(&self, device: &wgpu::Device, label: &str, num_samples: u32) -> wgpu::Buffer {
|
fn create_poisson_disc_buffer(
|
||||||
|
&self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
label: &str,
|
||||||
|
dimension: u32,
|
||||||
|
num_samples: u32,
|
||||||
|
) -> wgpu::Buffer {
|
||||||
|
debug_assert!(
|
||||||
|
dimension == 2 || dimension == 3,
|
||||||
|
"unknown dimension {dimension}, expected 2 (2d) or 3 (3d)"
|
||||||
|
);
|
||||||
|
|
||||||
device.create_buffer(&wgpu::BufferDescriptor {
|
device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some(label),
|
label: Some(label),
|
||||||
size: mem::size_of::<glam::Vec2>() as u64 * (num_samples * 2) as u64,
|
size: mem::size_of::<glam::Vec2>() as u64 * (num_samples * dimension) as u64,
|
||||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||||
mapped_at_creation: false,
|
mapped_at_creation: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate and write a Poisson disc to `buffer` with `num_pcf_samples.pow(2)` amount of points.
|
/// 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_samples: u32) {
|
fn write_poisson_disc(
|
||||||
let num_floats = num_samples * 2; // points are vec2f
|
&self,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
buffer: &wgpu::Buffer,
|
||||||
|
dimension: u32,
|
||||||
|
num_samples: u32,
|
||||||
|
) {
|
||||||
|
debug_assert!(
|
||||||
|
dimension == 2 || dimension == 3,
|
||||||
|
"unknown dimension {dimension}, expected 2 (2d) or 3 (3d)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let num_floats = num_samples * dimension; // points are vec2f
|
||||||
let min_dist = (num_floats as f32).sqrt() / num_floats as f32;
|
let min_dist = (num_floats as f32).sqrt() / num_floats as f32;
|
||||||
let mut points = vec![];
|
let mut points = vec![];
|
||||||
|
|
||||||
// use a while loop to ensure that the correct number of floats is created
|
// use a while loop to ensure that the correct number of floats is created
|
||||||
while points.len() < num_floats as usize {
|
while points.len() < num_floats as usize {
|
||||||
|
if dimension == 2 {
|
||||||
let poisson = Poisson2D::new()
|
let poisson = Poisson2D::new()
|
||||||
.with_dimensions([1.0, 1.0], min_dist)
|
.with_dimensions([1.0, 1.0], min_dist)
|
||||||
.with_samples(num_samples);
|
.with_samples(num_samples);
|
||||||
|
|
||||||
points = poisson.iter().flatten()
|
points = poisson
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
.map(|p| p * 2.0 - 1.0)
|
.map(|p| p * 2.0 - 1.0)
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
} else if dimension == 3 {
|
||||||
|
let poisson = Poisson3D::new()
|
||||||
|
.with_dimensions([1.0, 1.0, 1.0], min_dist)
|
||||||
|
.with_samples(num_samples);
|
||||||
|
|
||||||
|
points = poisson
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|p| p * 2.0 - 1.0)
|
||||||
|
.collect_vec();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
points.truncate(num_floats as _);
|
points.truncate(num_floats as _);
|
||||||
|
|
||||||
|
@ -441,7 +512,25 @@ impl Node for ShadowMapsPass {
|
||||||
ShadowMapsPassSlots::PcfPoissonDiscBuffer,
|
ShadowMapsPassSlots::PcfPoissonDiscBuffer,
|
||||||
SlotAttribute::Output,
|
SlotAttribute::Output,
|
||||||
Some(SlotValue::Buffer(Arc::new(
|
Some(SlotValue::Buffer(Arc::new(
|
||||||
self.create_poisson_disc_buffer(device, "buffer_poisson_disc_pcf", PCF_SAMPLES_NUM_MAX),
|
self.create_poisson_disc_buffer(
|
||||||
|
device,
|
||||||
|
"buffer_poisson_disc_pcf",
|
||||||
|
2,
|
||||||
|
PCF_SAMPLES_NUM_MAX,
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
|
||||||
|
node.add_buffer_slot(
|
||||||
|
ShadowMapsPassSlots::PcfPoissonDiscBuffer3d,
|
||||||
|
SlotAttribute::Output,
|
||||||
|
Some(SlotValue::Buffer(Arc::new(
|
||||||
|
self.create_poisson_disc_buffer(
|
||||||
|
device,
|
||||||
|
"buffer_poisson_disc_pcf_3d",
|
||||||
|
3,
|
||||||
|
PCF_SAMPLES_NUM_MAX,
|
||||||
|
),
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -449,7 +538,12 @@ impl Node for ShadowMapsPass {
|
||||||
ShadowMapsPassSlots::PcssPoissonDiscBuffer,
|
ShadowMapsPassSlots::PcssPoissonDiscBuffer,
|
||||||
SlotAttribute::Output,
|
SlotAttribute::Output,
|
||||||
Some(SlotValue::Buffer(Arc::new(
|
Some(SlotValue::Buffer(Arc::new(
|
||||||
self.create_poisson_disc_buffer(device, "buffer_poisson_disc_pcss", PCSS_SAMPLES_NUM_MAX),
|
self.create_poisson_disc_buffer(
|
||||||
|
device,
|
||||||
|
"buffer_poisson_disc_pcss",
|
||||||
|
2,
|
||||||
|
PCSS_SAMPLES_NUM_MAX,
|
||||||
|
),
|
||||||
))),
|
))),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -462,28 +556,43 @@ impl Node for ShadowMapsPass {
|
||||||
world: &mut lyra_ecs::World,
|
world: &mut lyra_ecs::World,
|
||||||
context: &mut crate::render::graph::RenderGraphContext,
|
context: &mut crate::render::graph::RenderGraphContext,
|
||||||
) {
|
) {
|
||||||
world.add_resource_default_if_absent::<ShadowSettings>();
|
world.add_resource_default_if_absent::<ShadowCasterSettings>();
|
||||||
if world.has_resource_changed::<ShadowSettings>() {
|
if world.has_resource_changed::<ShadowCasterSettings>() {
|
||||||
debug!("Detected change in ShadowSettings, recreating poisson disks");
|
debug!("Detected change in ShadowSettings, recreating poisson disks");
|
||||||
|
|
||||||
let settings = world.get_resource::<ShadowSettings>();
|
let settings = world.get_resource::<ShadowCasterSettings>();
|
||||||
// convert to uniform now since the from impl limits to max values
|
// convert to uniform now since the from impl limits to max values
|
||||||
let uniform = ShadowSettingsUniform::from(*settings);
|
let uniform = ShadowSettingsUniform::from(*settings);
|
||||||
|
|
||||||
let buffer = graph.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
|
let buffer = graph
|
||||||
.unwrap().as_buffer().unwrap();
|
.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
|
||||||
self.write_poisson_disc(&context.queue, &buffer, uniform.pcf_samples_num);
|
.unwrap()
|
||||||
|
.as_buffer()
|
||||||
|
.unwrap();
|
||||||
|
self.write_poisson_disc(&context.queue, &buffer, 2, uniform.pcf_samples_num);
|
||||||
|
|
||||||
let buffer = graph.slot_value(ShadowMapsPassSlots::PcssPoissonDiscBuffer)
|
let buffer = graph
|
||||||
.unwrap().as_buffer().unwrap();
|
.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer3d)
|
||||||
self.write_poisson_disc(&context.queue, &buffer, uniform.pcss_blocker_search_samples);
|
.unwrap()
|
||||||
|
.as_buffer()
|
||||||
|
.unwrap();
|
||||||
|
self.write_poisson_disc(&context.queue, &buffer, 3, uniform.pcf_samples_num);
|
||||||
|
|
||||||
context.queue_buffer_write_with(
|
let buffer = graph
|
||||||
ShadowMapsPassSlots::ShadowSettingsUniform,
|
.slot_value(ShadowMapsPassSlots::PcssPoissonDiscBuffer)
|
||||||
0,
|
.unwrap()
|
||||||
uniform,
|
.as_buffer()
|
||||||
|
.unwrap();
|
||||||
|
self.write_poisson_disc(
|
||||||
|
&context.queue,
|
||||||
|
&buffer,
|
||||||
|
2,
|
||||||
|
uniform.pcss_blocker_search_samples,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
context.queue_buffer_write_with(ShadowMapsPassSlots::ShadowSettingsUniform, 0, uniform);
|
||||||
}
|
}
|
||||||
|
let settings = *world.get_resource::<ShadowCasterSettings>();
|
||||||
|
|
||||||
self.render_meshes = world.try_get_resource_data::<RenderMeshes>();
|
self.render_meshes = world.try_get_resource_data::<RenderMeshes>();
|
||||||
self.transform_buffers = world.try_get_resource_data::<TransformBuffers>();
|
self.transform_buffers = world.try_get_resource_data::<TransformBuffers>();
|
||||||
|
@ -494,23 +603,48 @@ 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, _) in world.view_iter::<(Entities, &Transform, Has<DirectionalLight>)>() {
|
for (entity, pos, shadow_settings, _) in world.view_iter::<(
|
||||||
|
Entities,
|
||||||
|
&Transform,
|
||||||
|
Option<&ShadowCasterSettings>,
|
||||||
|
Has<DirectionalLight>,
|
||||||
|
)>() {
|
||||||
if !self.depth_maps.contains_key(&entity) {
|
if !self.depth_maps.contains_key(&entity) {
|
||||||
|
let (custom_settings, shadow_settings) = shadow_settings
|
||||||
|
.map(|ss| (true, ss.clone()))
|
||||||
|
.unwrap_or((false, settings));
|
||||||
|
|
||||||
let atlas_index = self.create_depth_map(
|
let atlas_index = self.create_depth_map(
|
||||||
&context.queue,
|
&context.queue,
|
||||||
LightType::Directional,
|
LightType::Directional,
|
||||||
entity,
|
entity,
|
||||||
*pos,
|
*pos,
|
||||||
45.0,
|
custom_settings,
|
||||||
|
shadow_settings,
|
||||||
);
|
);
|
||||||
index_components_queue.push_back((entity, atlas_index));
|
index_components_queue.push_back((entity, atlas_index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (entity, pos, _) in world.view_iter::<(Entities, &Transform, Has<PointLight>)>() {
|
for (entity, pos, shadow_settings, _) in world.view_iter::<(
|
||||||
|
Entities,
|
||||||
|
&Transform,
|
||||||
|
Option<&ShadowCasterSettings>,
|
||||||
|
Has<PointLight>,
|
||||||
|
)>() {
|
||||||
if !self.depth_maps.contains_key(&entity) {
|
if !self.depth_maps.contains_key(&entity) {
|
||||||
let atlas_index =
|
let (custom_settings, shadow_settings) = shadow_settings
|
||||||
self.create_depth_map(&context.queue, LightType::Point, entity, *pos, 30.0);
|
.map(|ss| (true, ss.clone()))
|
||||||
|
.unwrap_or((false, settings));
|
||||||
|
|
||||||
|
let atlas_index = self.create_depth_map(
|
||||||
|
&context.queue,
|
||||||
|
LightType::Point,
|
||||||
|
entity,
|
||||||
|
*pos,
|
||||||
|
custom_settings,
|
||||||
|
shadow_settings,
|
||||||
|
);
|
||||||
index_components_queue.push_back((entity, atlas_index));
|
index_components_queue.push_back((entity, atlas_index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -736,6 +870,38 @@ fn light_shadow_pass_impl<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shadow casting settings for a light caster.
|
||||||
|
///
|
||||||
|
/// Put this on an entity with a light source to override the global shadow
|
||||||
|
/// settings, the [`ShadowSettings`] resource.
|
||||||
|
#[derive(Debug, Copy, Clone, Component)]
|
||||||
|
pub struct ShadowCasterSettings {
|
||||||
|
pub filtering_mode: ShadowFilteringMode,
|
||||||
|
/// How many PCF filtering samples are used per dimension.
|
||||||
|
///
|
||||||
|
/// A value of 25 is common, this is maxed to 128.
|
||||||
|
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 25 is common, this is maxed to 128.
|
||||||
|
pub pcss_blocker_search_samples: u32,
|
||||||
|
pub near_plane: f32,
|
||||||
|
pub far_plane: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ShadowCasterSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
filtering_mode: ShadowFilteringMode::default(),
|
||||||
|
pcf_samples_num: 25,
|
||||||
|
pcss_blocker_search_samples: 25,
|
||||||
|
near_plane: 0.1,
|
||||||
|
far_plane: 45.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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 {
|
||||||
|
@ -747,7 +913,11 @@ pub struct LightShadowUniform {
|
||||||
light_size_uv: f32,
|
light_size_uv: f32,
|
||||||
_padding1: u32,
|
_padding1: u32,
|
||||||
light_pos: glam::Vec3,
|
light_pos: glam::Vec3,
|
||||||
_padding2: u32,
|
/// Boolean casted as integer
|
||||||
|
has_shadow_settings: u32,
|
||||||
|
pcf_samples_num: u32,
|
||||||
|
pcss_blocker_search_samples: u32,
|
||||||
|
_padding2: [u32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -794,7 +964,7 @@ pub enum ShadowFilteringMode {
|
||||||
Pcss,
|
Pcss,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
/* #[derive(Debug, Copy, Clone)]
|
||||||
pub struct ShadowSettings {
|
pub struct ShadowSettings {
|
||||||
pub filtering_mode: ShadowFilteringMode,
|
pub filtering_mode: ShadowFilteringMode,
|
||||||
/// How many PCF filtering samples are used per dimension.
|
/// How many PCF filtering samples are used per dimension.
|
||||||
|
@ -816,7 +986,7 @@ impl Default for ShadowSettings {
|
||||||
pcss_blocker_search_samples: 25,
|
pcss_blocker_search_samples: 25,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
const PCF_SAMPLES_NUM_MAX: u32 = 128;
|
const PCF_SAMPLES_NUM_MAX: u32 = 128;
|
||||||
const PCSS_SAMPLES_NUM_MAX: u32 = 128;
|
const PCSS_SAMPLES_NUM_MAX: u32 = 128;
|
||||||
|
@ -834,12 +1004,27 @@ struct ShadowSettingsUniform {
|
||||||
pcss_blocker_search_samples: u32,
|
pcss_blocker_search_samples: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ShadowSettings> for ShadowSettingsUniform {
|
impl From<ShadowCasterSettings> for ShadowSettingsUniform {
|
||||||
fn from(value: ShadowSettings) -> Self {
|
fn from(value: ShadowCasterSettings) -> Self {
|
||||||
let raw_pcf_samples = value.pcf_samples_num.min(PCF_SAMPLES_NUM_MAX);
|
Self::new(
|
||||||
let raw_pcss_samples = value.pcss_blocker_search_samples.min(PCSS_SAMPLES_NUM_MAX);
|
value.filtering_mode,
|
||||||
|
value.pcf_samples_num,
|
||||||
|
value.pcss_blocker_search_samples,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (pcf_samples, pcss_samples) = match value.filtering_mode {
|
impl ShadowSettingsUniform {
|
||||||
|
/// Create a new shadow settings uniform.
|
||||||
|
pub fn new(
|
||||||
|
filter_mode: ShadowFilteringMode,
|
||||||
|
pcf_samples_num: u32,
|
||||||
|
pcss_blocker_search_samples: u32,
|
||||||
|
) -> Self {
|
||||||
|
let raw_pcf_samples = pcf_samples_num.min(PCF_SAMPLES_NUM_MAX);
|
||||||
|
let raw_pcss_samples = pcss_blocker_search_samples.min(PCSS_SAMPLES_NUM_MAX);
|
||||||
|
|
||||||
|
let (pcf_samples, pcss_samples) = match filter_mode {
|
||||||
ShadowFilteringMode::None => (0, 0),
|
ShadowFilteringMode::None => (0, 0),
|
||||||
ShadowFilteringMode::Pcf2x2 => (2, 0),
|
ShadowFilteringMode::Pcf2x2 => (2, 0),
|
||||||
ShadowFilteringMode::Pcf => (raw_pcf_samples, 0),
|
ShadowFilteringMode::Pcf => (raw_pcf_samples, 0),
|
||||||
|
|
|
@ -119,6 +119,10 @@ struct LightShadowMapUniform {
|
||||||
far_plane: f32,
|
far_plane: f32,
|
||||||
light_size_uv: f32,
|
light_size_uv: f32,
|
||||||
light_pos: vec3<f32>,
|
light_pos: vec3<f32>,
|
||||||
|
/// boolean casted as u32
|
||||||
|
has_shadow_settings: u32,
|
||||||
|
pcf_samples_num: u32,
|
||||||
|
pcss_blocker_search_samples: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ShadowSettingsUniform {
|
struct ShadowSettingsUniform {
|
||||||
|
@ -144,6 +148,8 @@ var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
|
||||||
@group(5) @binding(5)
|
@group(5) @binding(5)
|
||||||
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
|
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
|
||||||
@group(5) @binding(6)
|
@group(5) @binding(6)
|
||||||
|
var<storage, read> u_pcf_poisson_disc_3d: array<vec3<f32>>;
|
||||||
|
@group(5) @binding(7)
|
||||||
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;
|
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
|
@ -180,7 +186,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let shadow = calc_shadow_dir_light(in.world_normal, light_dir, frag_pos_light_space, atlas_dimensions, shadow_u);
|
let shadow = calc_shadow_dir_light(in.world_normal, light_dir, frag_pos_light_space, atlas_dimensions, shadow_u);
|
||||||
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) {
|
||||||
let shadow = calc_shadow_point(in.world_position, in.world_normal, light_dir, light, atlas_dimensions);
|
let shadow = calc_shadow_point_light(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);
|
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);
|
||||||
|
@ -249,6 +255,16 @@ fn coords_to_cube_atlas(tex_coord: vec3<f32>) -> vec3<f32> {
|
||||||
return vec3<f32>(res, f32(cube_idx));
|
return vec3<f32>(res, f32(cube_idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get shadow settings for a light.
|
||||||
|
/// Returns x as `pcf_samples_num` and y as `pcss_blocker_search_samples`.
|
||||||
|
fn get_shadow_settings(shadow_u: LightShadowMapUniform) -> vec2<u32> {
|
||||||
|
if shadow_u.has_shadow_settings == 1u {
|
||||||
|
return vec2<u32>(shadow_u.pcf_samples_num, shadow_u.pcss_blocker_search_samples);
|
||||||
|
} else {
|
||||||
|
return vec2<u32>(u_shadow_settings.pcf_samples_num, u_shadow_settings.pcss_blocker_search_samples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: vec4<f32>, atlas_dimensions: vec2<i32>, shadow_u: LightShadowMapUniform) -> f32 {
|
fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: vec4<f32>, atlas_dimensions: vec2<i32>, shadow_u: LightShadowMapUniform) -> 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 flipped after transforming
|
// for some reason the y component is flipped after transforming
|
||||||
|
@ -261,21 +277,28 @@ fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light
|
||||||
let bias = 0.005;//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;
|
let current_depth = proj_coords.z - bias;
|
||||||
|
|
||||||
|
// get settings
|
||||||
|
let settings = get_shadow_settings(shadow_u);
|
||||||
|
let pcf_samples_num = settings.x;
|
||||||
|
let pcss_blocker_search_samples = settings.y;
|
||||||
|
|
||||||
var shadow = 0.0;
|
var shadow = 0.0;
|
||||||
if u_shadow_settings.pcf_samples_num > 0u && u_shadow_settings.pcss_blocker_search_samples > 0u {
|
// hardware 2x2 PCF via camparison sampler
|
||||||
|
if pcf_samples_num == 2u {
|
||||||
|
let region_coords = to_atlas_frame_coords(shadow_u, xy_remapped);
|
||||||
|
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
|
||||||
|
}
|
||||||
|
// PCSS
|
||||||
|
else if pcf_samples_num > 0u && pcss_blocker_search_samples > 0u {
|
||||||
shadow = pcss_dir_light(xy_remapped, current_depth, shadow_u);
|
shadow = pcss_dir_light(xy_remapped, current_depth, shadow_u);
|
||||||
}
|
}
|
||||||
// hardware 2x2 PCF via camparison sampler
|
// only PCF
|
||||||
else if u_shadow_settings.pcf_samples_num == 2u {
|
else if pcf_samples_num > 0u {
|
||||||
let region_coords = to_atlas_frame_coords(shadow_u, xy_remapped);
|
let texel_size = 1.0 / f32(shadow_u.atlas_frame.width);
|
||||||
shadow = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
|
|
||||||
} else if u_shadow_settings.pcf_samples_num > 0u {
|
|
||||||
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
|
|
||||||
// TODO: should texel size be using the entire atlas dimensions, or just the frame?
|
|
||||||
let texel_size = 1.0 / f32(atlas_dimensions.x); // f32(shadow_u.atlas_frame.width)
|
|
||||||
|
|
||||||
shadow = pcf_dir_light(xy_remapped, current_depth, shadow_u, texel_size);
|
shadow = pcf_dir_light(xy_remapped, current_depth, shadow_u, texel_size);
|
||||||
} else { // pcf_samples_num == 0
|
}
|
||||||
|
// no filtering
|
||||||
|
else {
|
||||||
let region_coords = to_atlas_frame_coords(shadow_u, xy_remapped);
|
let region_coords = to_atlas_frame_coords(shadow_u, xy_remapped);
|
||||||
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
|
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
|
||||||
shadow = select(1.0, 0.0, current_depth > closest_depth);
|
shadow = select(1.0, 0.0, current_depth > closest_depth);
|
||||||
|
@ -304,14 +327,19 @@ fn to_atlas_frame_coords(shadow_u: LightShadowMapUniform, coords: vec2<f32>) ->
|
||||||
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
|
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
|
||||||
|
|
||||||
// get the rect of the frame as a vec4
|
// 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));
|
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
|
// put the frame rect in atlas UV space
|
||||||
region_rect /= f32(atlas_dimensions.x);
|
region_rect /= f32(atlas_dimensions.x);
|
||||||
|
|
||||||
|
// calculate a relatively tiny offset to avoid getting the end of the frame and causing
|
||||||
|
// linear or nearest filtering to bleed to the adjacent frame.
|
||||||
|
let texel_size = (1.0 / f32(shadow_u.atlas_frame.x)) * 4.0;
|
||||||
|
|
||||||
// lerp input coords
|
// lerp input coords
|
||||||
let region_coords = vec2<f32>(
|
let region_coords = vec2<f32>(
|
||||||
mix(region_rect.x, region_rect.x + region_rect.z, coords.x),
|
mix(region_rect.x + texel_size, region_rect.x + region_rect.z - texel_size, coords.x),
|
||||||
mix(region_rect.y, region_rect.y + region_rect.w, coords.y)
|
mix(region_rect.y + texel_size, region_rect.y + region_rect.w - texel_size, coords.y)
|
||||||
);
|
);
|
||||||
|
|
||||||
return region_coords;
|
return region_coords;
|
||||||
|
@ -372,45 +400,96 @@ fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMa
|
||||||
return saturate(shadow);
|
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 {
|
fn calc_shadow_point_light(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;
|
var frag_to_light = world_pos - light.position;
|
||||||
let temp = coords_to_cube_atlas(normalize(frag_to_light));
|
let temp = coords_to_cube_atlas(normalize(frag_to_light));
|
||||||
var coords_2d = temp.xy;
|
var coords_2d = temp.xy;
|
||||||
let cube_idx = i32(temp.z);
|
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;
|
var indices = light.light_shadow_uniform_index;
|
||||||
let i = indices[cube_idx - 1];
|
let i = indices[cube_idx - 1];
|
||||||
let u: LightShadowMapUniform = u_light_shadow[i];
|
let u: LightShadowMapUniform = u_light_shadow[i];
|
||||||
|
|
||||||
// get the atlas frame in [0; 1] in the atlas texture
|
let uniforms = array<LightShadowMapUniform, 6>(
|
||||||
// z is width, w is height
|
u_light_shadow[indices[0]],
|
||||||
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));
|
u_light_shadow[indices[1]],
|
||||||
region_coords /= f32(atlas_dimensions.x);
|
u_light_shadow[indices[2]],
|
||||||
|
u_light_shadow[indices[3]],
|
||||||
|
u_light_shadow[indices[4]],
|
||||||
|
u_light_shadow[indices[5]]
|
||||||
|
);
|
||||||
|
|
||||||
// simulate `ClampToBorder`, not creating shadows past the shadow map regions
|
var current_depth = length(frag_to_light);
|
||||||
/*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);
|
|
||||||
|
|
||||||
// use a bias to avoid shadow acne
|
|
||||||
let bias = max(0.05 * (1.0 - dot(world_normal, light_dir)), 0.005);
|
|
||||||
var current_depth = length(frag_to_light) - bias;
|
|
||||||
current_depth /= u.far_plane;
|
current_depth /= u.far_plane;
|
||||||
|
current_depth -= 0.005; // TODO: find a better way to calculate bias
|
||||||
|
|
||||||
var shadow = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, coords_2d, current_depth);
|
// get settings
|
||||||
|
let settings = get_shadow_settings(u);
|
||||||
|
let pcf_samples_num = settings.x;
|
||||||
|
let pcss_blocker_search_samples = settings.y;
|
||||||
|
|
||||||
|
var shadow = 0.0;
|
||||||
|
// hardware 2x2 PCF via camparison sampler
|
||||||
|
if pcf_samples_num == 2u {
|
||||||
|
let region_coords = to_atlas_frame_coords(u, coords_2d);
|
||||||
|
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
|
||||||
|
}
|
||||||
|
// PCSS
|
||||||
|
else if pcf_samples_num > 0u && pcss_blocker_search_samples > 0u {
|
||||||
|
shadow = pcss_dir_light(coords_2d, current_depth, u);
|
||||||
|
}
|
||||||
|
// only PCF
|
||||||
|
else if pcf_samples_num > 0u {
|
||||||
|
let texel_size = 1.0 / f32(u.atlas_frame.width);
|
||||||
|
shadow = pcf_point_light(frag_to_light, current_depth, uniforms, pcf_samples_num, 0.007);
|
||||||
|
//shadow = pcf_point_light(coords_2d, current_depth, u, pcf_samples_num, texel_size);
|
||||||
|
}
|
||||||
|
// no filtering
|
||||||
|
else {
|
||||||
|
let region_coords = to_atlas_frame_coords(u, coords_2d);
|
||||||
|
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
|
||||||
|
shadow = select(1.0, 0.0, current_depth > closest_depth);
|
||||||
|
}
|
||||||
|
|
||||||
return shadow;
|
return shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the shadow coefficient using PCF of a directional light
|
||||||
|
fn pcf_point_light(tex_coords: vec3<f32>, test_depth: f32, shadow_us: array<LightShadowMapUniform, 6>, samples_num: u32, uv_radius: f32) -> f32 {
|
||||||
|
var shadow_unis = shadow_us;
|
||||||
|
|
||||||
|
var shadow = 0.0;
|
||||||
|
for (var i = 0; i < i32(samples_num); i++) {
|
||||||
|
var temp = coords_to_cube_atlas(tex_coords);
|
||||||
|
var coords_2d = temp.xy;
|
||||||
|
var cube_idx = i32(temp.z);
|
||||||
|
var shadow_u = shadow_unis[cube_idx - 1];
|
||||||
|
|
||||||
|
coords_2d += u_pcf_poisson_disc[i] * uv_radius;
|
||||||
|
|
||||||
|
let new_coords = to_atlas_frame_coords(shadow_u, coords_2d);
|
||||||
|
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 pcf_point_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, samples_num: u32, uv_radius: f32) -> f32 {
|
||||||
|
var shadow = 0.0;
|
||||||
|
for (var i = 0; i < i32(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 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));
|
||||||
|
@ -485,7 +564,8 @@ 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 + (1.0 - shadow) * (diffuse_color + specular_color)) * point_light.intensity;
|
//return (ambient_color + shadow * (diffuse_color + specular_color)) * point_light.intensity;
|
||||||
|
return (shadow * (ambient_color + 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> {
|
||||||
|
|
|
@ -13,7 +13,12 @@ struct LightShadowMapUniform {
|
||||||
atlas_frame: TextureAtlasFrame,
|
atlas_frame: TextureAtlasFrame,
|
||||||
near_plane: f32,
|
near_plane: f32,
|
||||||
far_plane: f32,
|
far_plane: f32,
|
||||||
|
light_size_uv: f32,
|
||||||
light_pos: vec3<f32>,
|
light_pos: vec3<f32>,
|
||||||
|
/// boolean casted as u32
|
||||||
|
has_shadow_settings: u32,
|
||||||
|
pcf_samples_num: u32,
|
||||||
|
pcss_blocker_search_samples: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
|
|
Loading…
Reference in New Issue