render: implement PCSS for directional lights
This commit is contained in:
parent
4c6c6c4dd5
commit
4449172c2b
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();
|
platform_gltf.wait_recurse_dependencies_load();
|
||||||
let platform_mesh = &platform_gltf.data_ref().unwrap().scenes[0];
|
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);
|
drop(resman);
|
||||||
|
|
||||||
// cube in the air
|
// 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 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
|
// cube on the right, on the ground
|
||||||
world.spawn((
|
world.spawn((
|
||||||
cube_mesh.clone(),
|
cube_mesh.clone(),
|
||||||
|
@ -149,10 +163,19 @@ fn setup_scene_plugin(game: &mut Game) {
|
||||||
WorldTransform::default(),
|
WorldTransform::default(),
|
||||||
//Transform::from_xyz(0.0, -5.0, -5.0),
|
//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)),
|
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.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||||
light_tran.rotate_y(math::Angle::Degrees(-35.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();
|
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.rotate_x(math::Angle::Degrees(-17.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_y(math::Angle::Degrees(-55.0));
|
||||||
world.spawn((camera, FreeFlyCamera::default()));
|
world.spawn((camera, FreeFlyCamera::default()));
|
||||||
}
|
}
|
|
@ -63,6 +63,14 @@ pub enum SlotValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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>> {
|
pub fn as_texture_view(&self) -> Option<&Arc<wgpu::TextureView>> {
|
||||||
bind_match!(self, Self::TextureView(v) => v)
|
bind_match!(self, Self::TextureView(v) => v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,11 @@ impl Node for MeshPass {
|
||||||
.expect("missing ShadowMapsPassSlots::ShadowAtlasSampler")
|
.expect("missing ShadowMapsPassSlots::ShadowAtlasSampler")
|
||||||
.as_sampler()
|
.as_sampler()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let atlas_sampler_compare = graph
|
||||||
|
.slot_value(ShadowMapsPassSlots::ShadowAtlasSamplerComparison)
|
||||||
|
.expect("missing ShadowMapsPassSlots::ShadowAtlasSamplerComparison")
|
||||||
|
.as_sampler()
|
||||||
|
.unwrap();
|
||||||
let shadow_settings_buf = graph
|
let shadow_settings_buf = graph
|
||||||
.slot_value(ShadowMapsPassSlots::ShadowSettingsUniform)
|
.slot_value(ShadowMapsPassSlots::ShadowSettingsUniform)
|
||||||
.expect("missing ShadowMapsPassSlots::ShadowSettingsUniform")
|
.expect("missing ShadowMapsPassSlots::ShadowSettingsUniform")
|
||||||
|
@ -130,6 +135,11 @@ impl Node for MeshPass {
|
||||||
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer")
|
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer")
|
||||||
.as_buffer()
|
.as_buffer()
|
||||||
.unwrap();
|
.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 {
|
let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("bgl_shadows_atlas"),
|
label: Some("bgl_shadows_atlas"),
|
||||||
|
@ -147,11 +157,17 @@ impl Node for MeshPass {
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 2,
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
ty: wgpu::BindingType::Buffer {
|
ty: wgpu::BindingType::Buffer {
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
@ -161,7 +177,7 @@ impl Node for MeshPass {
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 3,
|
binding: 4,
|
||||||
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 },
|
||||||
|
@ -171,7 +187,17 @@ impl Node for MeshPass {
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
wgpu::BindGroupLayoutEntry {
|
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,
|
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 },
|
||||||
|
@ -197,6 +223,10 @@ impl Node for MeshPass {
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 2,
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Sampler(atlas_sampler_compare),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
buffer: shadow_settings_buf,
|
buffer: shadow_settings_buf,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
@ -204,7 +234,7 @@ impl Node for MeshPass {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 3,
|
binding: 4,
|
||||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
buffer: light_uniform_buf,
|
buffer: light_uniform_buf,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
@ -212,13 +242,21 @@ impl Node for MeshPass {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 4,
|
binding: 5,
|
||||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||||
buffer: pcf_poisson_disc,
|
buffer: pcf_poisson_disc,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
size: None,
|
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 fast_poisson::Poisson2D;
|
||||||
|
use glam::Vec2;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lyra_ecs::{
|
use lyra_ecs::{
|
||||||
query::{filter::Has, Entities},
|
query::{filter::Has, Entities},
|
||||||
|
@ -25,18 +26,19 @@ use crate::render::{
|
||||||
|
|
||||||
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
|
||||||
|
|
||||||
const PCF_SAMPLES_NUM: u32 = 6;
|
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 {
|
||||||
ShadowAtlasTexture,
|
ShadowAtlasTexture,
|
||||||
ShadowAtlasTextureView,
|
ShadowAtlasTextureView,
|
||||||
ShadowAtlasSampler,
|
ShadowAtlasSampler,
|
||||||
|
ShadowAtlasSamplerComparison,
|
||||||
ShadowAtlasSizeBuffer,
|
ShadowAtlasSizeBuffer,
|
||||||
ShadowLightUniformsBuffer,
|
ShadowLightUniformsBuffer,
|
||||||
ShadowSettingsUniform,
|
ShadowSettingsUniform,
|
||||||
PcfPoissonDiscBuffer,
|
PcfPoissonDiscBuffer,
|
||||||
|
PcssPoissonDiscBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
||||||
|
@ -74,6 +76,7 @@ pub struct ShadowMapsPass {
|
||||||
atlas: LightShadowMapAtlas,
|
atlas: LightShadowMapAtlas,
|
||||||
/// The depth map atlas sampler
|
/// The depth map atlas sampler
|
||||||
atlas_sampler: Rc<wgpu::Sampler>,
|
atlas_sampler: Rc<wgpu::Sampler>,
|
||||||
|
atlas_sampler_compare: Rc<wgpu::Sampler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShadowMapsPass {
|
impl ShadowMapsPass {
|
||||||
|
@ -98,7 +101,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 * 8,
|
SHADOW_SIZE * 2,
|
||||||
);
|
);
|
||||||
|
|
||||||
let atlas_size_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let atlas_size_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
@ -107,16 +110,28 @@ impl ShadowMapsPass {
|
||||||
contents: bytemuck::bytes_of(&atlas.atlas_size()),
|
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 {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
label: Some("sampler_shadow_map_atlas"),
|
label: Some("sampler_shadow_map_atlas"),
|
||||||
address_mode_u: wgpu::AddressMode::ClampToBorder,
|
address_mode_u: wgpu::AddressMode::ClampToBorder,
|
||||||
address_mode_v: wgpu::AddressMode::ClampToBorder,
|
address_mode_v: wgpu::AddressMode::ClampToBorder,
|
||||||
address_mode_w: wgpu::AddressMode::ClampToBorder,
|
address_mode_w: wgpu::AddressMode::ClampToBorder,
|
||||||
mag_filter: wgpu::FilterMode::Linear,
|
mag_filter: wgpu::FilterMode::Nearest,
|
||||||
min_filter: wgpu::FilterMode::Linear,
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
mipmap_filter: wgpu::FilterMode::Linear,
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
border_color: Some(wgpu::SamplerBorderColor::OpaqueWhite),
|
border_color: Some(wgpu::SamplerBorderColor::OpaqueWhite),
|
||||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -155,6 +170,7 @@ impl ShadowMapsPass {
|
||||||
point_light_pipeline: None,
|
point_light_pipeline: None,
|
||||||
|
|
||||||
atlas_sampler: Rc::new(sampler),
|
atlas_sampler: Rc::new(sampler),
|
||||||
|
atlas_sampler_compare: Rc::new(sampler_compare),
|
||||||
atlas: LightShadowMapAtlas(Arc::new(RwLock::new(atlas))),
|
atlas: LightShadowMapAtlas(Arc::new(RwLock::new(atlas))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +199,14 @@ impl ShadowMapsPass {
|
||||||
|
|
||||||
let projection =
|
let projection =
|
||||||
glam::Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, NEAR_PLANE, FAR_PLANE);
|
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(
|
let look_view = glam::Mat4::look_to_rh(
|
||||||
light_pos.translation,
|
light_pos.translation,
|
||||||
light_pos.forward(),
|
light_pos.forward(),
|
||||||
|
@ -196,7 +220,8 @@ impl ShadowMapsPass {
|
||||||
atlas_frame,
|
atlas_frame,
|
||||||
near_plane: NEAR_PLANE,
|
near_plane: NEAR_PLANE,
|
||||||
far_plane,
|
far_plane,
|
||||||
_padding1: [0; 2],
|
light_size_uv,
|
||||||
|
_padding1: 0,
|
||||||
light_pos: light_pos.translation,
|
light_pos: light_pos.translation,
|
||||||
_padding2: 0,
|
_padding2: 0,
|
||||||
};
|
};
|
||||||
|
@ -284,7 +309,8 @@ impl ShadowMapsPass {
|
||||||
atlas_frame: frames[i],
|
atlas_frame: frames[i],
|
||||||
near_plane: NEAR_PLANE,
|
near_plane: NEAR_PLANE,
|
||||||
far_plane,
|
far_plane,
|
||||||
_padding1: [0; 2],
|
light_size_uv: 0.0,
|
||||||
|
_padding1: 0,
|
||||||
light_pos: light_trans,
|
light_pos: light_trans,
|
||||||
_padding2: 0,
|
_padding2: 0,
|
||||||
},
|
},
|
||||||
|
@ -322,26 +348,29 @@ impl ShadowMapsPass {
|
||||||
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, num_samples: u32) -> wgpu::Buffer {
|
||||||
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.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,
|
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_pcf_samples: u32) {
|
fn write_poisson_disc(&self, queue: &wgpu::Queue, buffer: &wgpu::Buffer, num_samples: u32) {
|
||||||
let num_points = num_pcf_samples.pow(2);
|
//let num_points = num_samples.pow(2);
|
||||||
let num_floats = num_points * 2; // points are vec2f
|
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_floats as f32).sqrt() / num_floats as f32;
|
||||||
|
//let min_dist = (num_samples as f32).sqrt() / num_samples 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 {
|
||||||
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_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 _);
|
points.truncate(num_floats as _);
|
||||||
|
@ -377,6 +406,12 @@ impl Node for ShadowMapsPass {
|
||||||
Some(SlotValue::Sampler(self.atlas_sampler.clone())),
|
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(
|
node.add_buffer_slot(
|
||||||
ShadowMapsPassSlots::ShadowLightUniformsBuffer,
|
ShadowMapsPassSlots::ShadowLightUniformsBuffer,
|
||||||
SlotAttribute::Output,
|
SlotAttribute::Output,
|
||||||
|
@ -404,11 +439,20 @@ impl Node for ShadowMapsPass {
|
||||||
Some(SlotValue::Buffer(Arc::new(settings_buffer))),
|
Some(SlotValue::Buffer(Arc::new(settings_buffer))),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let def_settings = ShadowSettings::default();
|
||||||
node.add_buffer_slot(
|
node.add_buffer_slot(
|
||||||
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),
|
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
|
// TODO: Update the poisson disc every time the PCF sampling point number changed
|
||||||
if !world.has_resource::<ShadowSettings>() {
|
if !world.has_resource::<ShadowSettings>() {
|
||||||
|
let def_settings = ShadowSettings::default();
|
||||||
let buffer = graph.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
|
let buffer = graph.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
|
||||||
.unwrap().as_buffer().unwrap();
|
.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
|
// TODO: only write buffer on changes to resource
|
||||||
|
@ -717,7 +766,9 @@ pub struct LightShadowUniform {
|
||||||
atlas_frame: AtlasFrame, // 2xUVec2 (4xf32), so no padding needed
|
atlas_frame: AtlasFrame, // 2xUVec2 (4xf32), so no padding needed
|
||||||
near_plane: f32,
|
near_plane: f32,
|
||||||
far_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,
|
light_pos: glam::Vec3,
|
||||||
_padding2: u32,
|
_padding2: u32,
|
||||||
}
|
}
|
||||||
|
@ -758,13 +809,22 @@ impl LightShadowMapAtlas {
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct ShadowSettings {
|
pub struct ShadowSettings {
|
||||||
|
/// How many PCF filtering samples are used per dimension.
|
||||||
|
///
|
||||||
|
/// A value of 16 is common.
|
||||||
pub pcf_samples_num: u32,
|
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 {
|
impl Default for ShadowSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
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)]
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
struct ShadowSettingsUniform {
|
struct ShadowSettingsUniform {
|
||||||
pcf_samples_num: u32,
|
pcf_samples_num: u32,
|
||||||
|
pcss_blocker_search_samples: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ShadowSettings> for ShadowSettingsUniform {
|
impl From<ShadowSettings> for ShadowSettingsUniform {
|
||||||
fn from(value: ShadowSettings) -> Self {
|
fn from(value: ShadowSettings) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pcf_samples_num: value.pcf_samples_num,
|
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,
|
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ShadowSettingsUniform {
|
struct ShadowSettingsUniform {
|
||||||
pcf_samples_num: u32,
|
pcf_samples_num: u32,
|
||||||
|
pcss_blocker_search_samples: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@group(4) @binding(0)
|
@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)
|
@group(5) @binding(0)
|
||||||
var t_shadow_maps_atlas: texture_depth_2d;
|
var t_shadow_maps_atlas: texture_depth_2d;
|
||||||
@group(5) @binding(1)
|
@group(5) @binding(1)
|
||||||
var s_shadow_maps_atlas: sampler_comparison;
|
var s_shadow_maps_atlas: sampler;
|
||||||
@group(5) @binding(2)
|
@group(5) @binding(2)
|
||||||
var<uniform> u_shadow_settings: ShadowSettingsUniform;
|
var s_shadow_maps_atlas_compare: sampler_comparison;
|
||||||
@group(5) @binding(3)
|
@group(5) @binding(3)
|
||||||
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
|
var<uniform> u_shadow_settings: ShadowSettingsUniform;
|
||||||
@group(5) @binding(4)
|
@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>>;
|
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
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
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);
|
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
|
/// 0 -> UNKNOWN
|
||||||
/// 1 -> right
|
/// 1 -> right
|
||||||
/// 2 -> left
|
/// 2 -> left
|
||||||
|
@ -194,7 +204,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
/// 4 -> bottom
|
/// 4 -> bottom
|
||||||
/// 5 -> near
|
/// 5 -> near
|
||||||
/// 6 -> far
|
/// 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_x = abs(tex_coord.x);
|
||||||
let abs_y = abs(tex_coord.y);
|
let abs_y = abs(tex_coord.y);
|
||||||
let abs_z = abs(tex_coord.z);
|
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 = (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.y = 1.0 - res.y;
|
||||||
//res.x = 1.0 - res.x;
|
|
||||||
|
|
||||||
return vec3<f32>(res, f32(cube_idx));
|
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
|
// 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;
|
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
|
// dont cast shadows outside the light's far plane
|
||||||
if (proj_coords.z > 1.0) {
|
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;
|
return shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
/// 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 {
|
fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, uv_radius: f32) -> 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));
|
|
||||||
|
|
||||||
// Sample PCF
|
|
||||||
var shadow = 0.0;
|
var shadow = 0.0;
|
||||||
var i = 0;
|
let samples_num = i32(u_shadow_settings.pcf_samples_num);
|
||||||
for (var x = -half_filter_size; x <= half_filter_size; x += 1.0) {
|
for (var i = 0; i < samples_num; i++) {
|
||||||
for (var y = -half_filter_size; y <= half_filter_size; y += 1.0) {
|
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
|
||||||
//let random = u_pcf_poisson_disc[i] * texel_size;
|
let new_coords = to_atlas_frame_coords(shadow_u, offset);
|
||||||
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 += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
|
||||||
shadow += pcf_depth;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
}
|
shadow /= f32(samples_num);
|
||||||
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;
|
// 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 {
|
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;
|
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;
|
var coords_2d = temp.xy;
|
||||||
let cube_idx = i32(temp.z);
|
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;
|
var current_depth = length(frag_to_light) - bias;
|
||||||
current_depth /= u.far_plane;
|
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;
|
return shadow;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue