render: PCF with poisson disc on directional lights

This commit is contained in:
SeanOMik 2024-07-14 22:14:08 -04:00
parent 27bc88c5a7
commit 4c6c6c4dd5
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
8 changed files with 261 additions and 44 deletions

135
Cargo.lock generated
View File

@ -65,6 +65,24 @@ dependencies = [
"memchr",
]
[[package]]
name = "aligned"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
dependencies = [
"as-slice",
]
[[package]]
name = "aligned-array"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c92d086290f52938013f6242ac62bf7d401fab8ad36798a609faa65c3fd2c"
dependencies = [
"generic-array",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
@ -122,6 +140,15 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "as-slice"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
dependencies = [
"stable_deref_trait",
]
[[package]]
name = "ash"
version = "0.37.3+1.3.251"
@ -351,6 +378,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "az"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "backtrace"
version = "0.3.69"
@ -786,6 +819,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "divrem"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82"
[[package]]
name = "dlib"
version = "0.5.2"
@ -795,6 +834,12 @@ dependencies = [
"libloading 0.8.1",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "downcast-rs"
version = "1.2.0"
@ -807,6 +852,12 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "elapsed"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f4e5af126dafd0741c2ad62d47f68b28602550102e5f0dd45c8a97fc8b49c29"
[[package]]
name = "elua"
version = "0.1.0"
@ -908,6 +959,18 @@ dependencies = [
"zune-inflate",
]
[[package]]
name = "fast_poisson"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2472baa9796d2ee497bd61690e3093a26935390d8ce0dd0ddc2db9b47a65898f"
dependencies = [
"kiddo",
"rand 0.8.5",
"rand_distr",
"rand_xoshiro",
]
[[package]]
name = "fastrand"
version = "1.9.0"
@ -953,6 +1016,19 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "fixed"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048"
dependencies = [
"az",
"bytemuck",
"half",
"num-traits",
"typenum",
]
[[package]]
name = "fixed-timestep-rotating-model"
version = "0.1.0"
@ -1683,6 +1759,26 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "kiddo"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1c5ea778d68eacd5c33f29537ba0b7b6c2595e74ee013a69cedc20ab4d3177"
dependencies = [
"aligned",
"aligned-array",
"az",
"divrem",
"doc-comment",
"elapsed",
"fixed",
"log",
"min-max-heap",
"num-traits",
"rand 0.8.5",
"rayon",
]
[[package]]
name = "kqueue"
version = "1.0.8"
@ -1750,6 +1846,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "libredox"
version = "0.0.2"
@ -1868,6 +1970,7 @@ dependencies = [
"bind_match",
"bytemuck",
"cfg-if",
"fast_poisson",
"gilrs-core",
"glam",
"image",
@ -2079,6 +2182,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "min-max-heap"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2687e6cf9c00f48e9284cf9fd15f2ef341d03cc7743abf9df4c5f07fdee50b18"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@ -2286,6 +2395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -2727,6 +2837,25 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rand_distr"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
dependencies = [
"num-traits",
"rand 0.8.5",
]
[[package]]
name = "rand_xoshiro"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "range-alloc"
version = "0.1.3"
@ -3226,6 +3355,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"

View File

@ -6,7 +6,7 @@ use lyra_engine::{
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
},
math::{self, Transform, Vec3},
render::light::{directional::DirectionalLight, PointLight},
render::light::directional::DirectionalLight,
scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
@ -161,7 +161,7 @@ fn setup_scene_plugin(game: &mut Game) {
DirectionalLight {
enabled: true,
color: Vec3::new(1.0, 0.95, 0.9),
intensity: 0.5,
intensity: 0.9,
},
light_tran,
));

View File

@ -39,6 +39,7 @@ rustc-hash = "1.1.0"
petgraph = { version = "0.6.5", features = ["matrix_graph"] }
bind_match = "0.1.2"
round_mult = "0.1.3"
fast_poisson = { version = "1.0.0", features = ["single_precision"] }
[features]
tracy = ["dep:tracing-tracy"]

View File

@ -125,6 +125,11 @@ impl Node for MeshPass {
.expect("missing ShadowMapsPassSlots::ShadowLightUniformsBuffer")
.as_buffer()
.unwrap();
let pcf_poisson_disc = graph
.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer")
.as_buffer()
.unwrap();
let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("bgl_shadows_atlas"),
@ -165,6 +170,16 @@ impl Node for MeshPass {
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
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,
},
],
});
@ -196,6 +211,14 @@ impl Node for MeshPass {
size: None,
}),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: pcf_poisson_disc,
offset: 0,
size: None,
}),
},
],
});

View File

@ -1,10 +1,9 @@
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 itertools::Itertools;
use lyra_ecs::{
query::{filter::Has, Entities},
AtomicRef, Component, Entity, ResourceData,
@ -16,12 +15,17 @@ use tracing::warn;
use wgpu::util::DeviceExt;
use crate::render::{
graph::{Node, NodeDesc, NodeType, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, TextureAtlas
graph::{Node, NodeDesc, NodeType, SlotAttribute, SlotValue},
light::{directional::DirectionalLight, LightType, PointLight},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
transform_buffer_storage::TransformBuffers,
vertex::Vertex,
AtlasFrame, GpuSlotBuffer, TextureAtlas,
};
use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
const PCF_SAMPLES_NUM: u32 = 4;
const PCF_SAMPLES_NUM: u32 = 6;
const SHADOW_SIZE: glam::UVec2 = glam::uvec2(1024, 1024);
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
@ -32,6 +36,7 @@ pub enum ShadowMapsPassSlots {
ShadowAtlasSizeBuffer,
ShadowLightUniformsBuffer,
ShadowSettingsUniform,
PcfPoissonDiscBuffer,
}
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
@ -174,8 +179,7 @@ impl ShadowMapsPass {
let atlas_index = atlas
.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.expect("failed to pack new shadow map into texture atlas");
let atlas_frame = atlas.texture_frame(atlas_index)
.expect("Frame missing");
let atlas_frame = atlas.texture_frame(atlas_index).expect("Frame missing");
let projection =
glam::Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, NEAR_PLANE, FAR_PLANE);
@ -253,24 +257,12 @@ impl ShadowMapsPass {
),
];
let atlas_idx_1 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_2 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_3 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_4 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_5 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_6 =
atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _)
.unwrap();
let atlas_idx_1 = atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _).unwrap();
let atlas_idx_2 = atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _).unwrap();
let atlas_idx_3 = atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _).unwrap();
let atlas_idx_4 = atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _).unwrap();
let atlas_idx_5 = atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _).unwrap();
let atlas_idx_6 = atlas.pack(SHADOW_SIZE.x as _, SHADOW_SIZE.y as _).unwrap();
let frames = [
atlas.texture_frame(atlas_idx_1).unwrap(),
@ -325,6 +317,37 @@ impl ShadowMapsPass {
fn mesh_buffers(&self) -> AtomicRef<RenderAssets<MeshBufferStorage>> {
self.mesh_buffers.as_ref().unwrap().get()
}
/// Create the gpu buffer for a poisson disc
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,
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
let min_dist = (num_floats as f32).sqrt() / num_floats 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);
points = poisson.iter().flatten().collect_vec();
}
points.truncate(num_floats as _);
queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice()));
}
}
impl Node for ShadowMapsPass {
@ -368,7 +391,8 @@ impl Node for ShadowMapsPass {
Some(SlotValue::Buffer(self.atlas_size_buffer.clone())),
);
let settings_buffer = graph.device().create_buffer(&wgpu::BufferDescriptor {
let device = graph.device();
let settings_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("buffer_shadow_settings"),
size: mem::size_of::<ShadowSettingsUniform>() as _,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
@ -380,6 +404,14 @@ impl Node for ShadowMapsPass {
Some(SlotValue::Buffer(Arc::new(settings_buffer))),
);
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),
))),
);
node
}
@ -390,9 +422,20 @@ impl Node for ShadowMapsPass {
context: &mut crate::render::graph::RenderGraphContext,
) {
{
// TODO: Update the poisson disc every time the PCF sampling point number changed
if !world.has_resource::<ShadowSettings>() {
let buffer = graph.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
.unwrap().as_buffer().unwrap();
self.write_poisson_disc(&context.queue, &buffer, ShadowSettings::default().pcf_samples_num);
}
// TODO: only write buffer on changes to resource
let shadow_settings = world.get_resource_or_default::<ShadowSettings>();
context.queue_buffer_write_with(ShadowMapsPassSlots::ShadowSettingsUniform, 0, ShadowSettingsUniform::from(*shadow_settings));
context.queue_buffer_write_with(
ShadowMapsPassSlots::ShadowSettingsUniform,
0,
ShadowSettingsUniform::from(*shadow_settings),
);
}
self.render_meshes = world.try_get_resource_data::<RenderMeshes>();
@ -425,8 +468,13 @@ impl Node for ShadowMapsPass {
for (entity, pos, _) in world.view_iter::<(Entities, &Transform, Has<DirectionalLight>)>() {
if !self.depth_maps.contains_key(&entity) {
// TODO: dont pack the textures as they're added
let atlas_index =
self.create_depth_map(&context.queue, LightType::Directional, entity, *pos, 45.0);
let atlas_index = self.create_depth_map(
&context.queue,
LightType::Directional,
entity,
*pos,
45.0,
);
index_components_queue.push_back((entity, atlas_index));
}
}
@ -553,12 +601,12 @@ impl Node for ShadowMapsPass {
});
for light_depth_map in self.depth_maps.values() {
match light_depth_map.light_type {
LightType::Directional => {
pass.set_pipeline(&pipeline);
let frame = atlas.texture_frame(light_depth_map.atlas_index)
let frame = atlas
.texture_frame(light_depth_map.atlas_index)
.expect("missing atlas frame for light");
light_shadow_pass_impl(
@ -570,12 +618,13 @@ impl Node for ShadowMapsPass {
&frame,
light_depth_map.uniform_index[0] as _,
);
},
}
LightType::Point => {
pass.set_pipeline(&point_light_pipeline);
for side in 0..6 {
let frame = atlas.texture_frame(light_depth_map.atlas_index + side)
let frame = atlas
.texture_frame(light_depth_map.atlas_index + side)
.expect("missing atlas frame of light");
let ui = light_depth_map.uniform_index[side as usize];
@ -589,7 +638,7 @@ impl Node for ShadowMapsPass {
ui as _,
);
}
},
}
LightType::Spotlight => todo!(),
}
}
@ -714,7 +763,9 @@ pub struct ShadowSettings {
impl Default for ShadowSettings {
fn default() -> Self {
Self { pcf_samples_num: PCF_SAMPLES_NUM }
Self {
pcf_samples_num: PCF_SAMPLES_NUM,
}
}
}
@ -731,4 +782,4 @@ impl From<ShadowSettings> for ShadowSettingsUniform {
pcf_samples_num: value.pcf_samples_num,
}
}
}
}

View File

@ -211,7 +211,7 @@ impl LightUniformBuffers {
lights.push(uniform);
}
assert!(lights.len() < self.max_light_count as _); // ensure we dont overwrite the buffer
assert!(lights.len() < self.max_light_count as usize); // ensure we dont overwrite the buffer
// write the amount of lights to the buffer, and right after that the list of lights.
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights.len()]));

View File

@ -137,6 +137,8 @@ var s_shadow_maps_atlas: sampler_comparison;
var<uniform> u_shadow_settings: ShadowSettingsUniform;
@group(5) @binding(3)
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
@group(5) @binding(4)
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
@ -287,11 +289,16 @@ fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMa
// 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 offset = tex_coords + vec2<f32>(x, y) * texel_size;
//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;
i++;
}
}
shadow /= pow(f32(u_shadow_settings.pcf_samples_num), 2.0);

View File

@ -198,7 +198,7 @@ impl SkylinePacker {
/* if r.bottom() < min_height
|| (r.bottom() == min_height && self.skylines[i].width < min_width as usize) */
if y + height < min_height ||
(y + height == min_height && self.skylines[i].width < min_width as _)
(y + height == min_height && self.skylines[i].width < min_width as usize)
{
min_height = y + height;
min_width = self.skylines[i].width as _;
@ -224,8 +224,8 @@ impl SkylinePacker {
width: frame.width as _
};
assert!(skyline.right() <= self.size.x as _);
assert!(skyline.y <= self.size.y as _);
assert!(skyline.right() <= self.size.x as usize);
assert!(skyline.y <= self.size.y as usize);
self.skylines.insert(i, skyline);