render: implement wgsl-preprocessor, split shaders
CI / build (push) Failing after 7m56s Details

This commit is contained in:
SeanOMik 2024-09-14 20:06:55 -04:00
parent 60c139f9b2
commit 2b44d7f354
13 changed files with 541 additions and 439 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "lyra-scripting/elua"]
path = lyra-scripting/elua
url = ../elua.git # git@git.seanomik.net:SeanOMik/elua.git
[submodule "wgsl-preprocessor"]
path = wgsl-preprocessor
url = git@git.seanomik.net:SeanOMik/wgsl-preprocessor.git

76
Cargo.lock generated
View File

@ -1996,6 +1996,7 @@ dependencies = [
"unique",
"uuid",
"wgpu",
"wgsl_preprocessor",
"winit",
]
@ -2642,6 +2643,51 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.51",
]
[[package]]
name = "pest_meta"
version = "2.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "petgraph"
version = "0.6.5"
@ -2917,9 +2963,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.4"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -3472,18 +3518,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.56"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
@ -3776,6 +3822,12 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicode-bidi"
version = "0.3.15"
@ -4158,6 +4210,18 @@ dependencies = [
"web-sys",
]
[[package]]
name = "wgsl_preprocessor"
version = "0.1.0"
dependencies = [
"pest",
"pest_derive",
"regex",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "widestring"
version = "0.5.1"

View File

@ -10,6 +10,7 @@ lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" }
wgsl_preprocessor = { path = "../wgsl-preprocessor" }
winit = "0.28.1"
wgpu = { version = "0.15.1", features = [ "expose-ids"] }

View File

@ -356,6 +356,7 @@ impl Game {
t.with(filter::Targets::new()
// done by prefix, so it includes all lyra subpackages
.with_target("lyra", Level::DEBUG)
.with_target("wgsl_preprocessor", Level::DEBUG)
.with_target("wgpu", Level::WARN)
.with_target("winit", Level::DEBUG)
.with_default(Level::INFO))

View File

@ -118,6 +118,7 @@ pub struct RenderGraph {
/// A directed graph used to determine dependencies of nodes.
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
view_target: Rc<RefCell<ViewTarget>>,
shader_prepoc: wgsl_preprocessor::Processor,
}
impl RenderGraph {
@ -131,6 +132,7 @@ impl RenderGraph {
bind_groups: Default::default(),
node_graph: Default::default(),
view_target,
shader_prepoc: wgsl_preprocessor::Processor::new(),
}
}
@ -510,6 +512,22 @@ impl RenderGraph {
pub fn view_target_mut(&self) -> RefMut<ViewTarget> {
self.view_target.borrow_mut()
}
/// Register a shader with the preprocessor.
///
/// This step also parses the shader and will return errors if it failed to parse.
///
/// Returns: The shader module import path if the module specified one.
#[inline(always)]
pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> {
self.shader_prepoc.parse_module(shader_src)
}
/// Preprocess a shader, returning the source.
#[inline(always)]
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
self.shader_prepoc.preprocess_module(shader_path)
}
}
pub struct SubGraphNode {

View File

@ -102,6 +102,11 @@ impl Node for MeshPass {
_: &mut RenderGraphContext,
) {
if self.pipeline.is_none() {
let shader_mod = graph.register_shader(include_str!("../../shaders/base.wgsl"))
.expect("failed to register shader").expect("base shader missing module");
let shader_src = graph.preprocess_shader(&shader_mod)
.expect("failed to preprocess shader");
let device = graph.device();
let surface_config_format = graph.view_target().format();
@ -295,8 +300,8 @@ impl Node for MeshPass {
let atlas_bgl = self.shadows_atlas.as_ref().unwrap().layout.clone();
let shader = Rc::new(Shader {
label: Some("base_shader".into()),
source: include_str!("../../shaders/base.wgsl").to_string(),
label: Some(shader_mod.into()),
source: shader_src,
});
let transforms = world

View File

@ -19,7 +19,7 @@ use tracing::{debug, warn};
use wgpu::util::DeviceExt;
use crate::render::{
graph::{Node, NodeDesc, NodeType, SlotAttribute, SlotValue},
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue},
light::{directional::DirectionalLight, LightType, PointLight, SpotLight},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
transform_buffer_storage::TransformBuffers,
@ -74,6 +74,7 @@ pub struct ShadowMapsPass {
transform_buffers: Option<ResourceData>,
render_meshes: Option<ResourceData>,
mesh_buffers: Option<ResourceData>,
shader: Option<String>,
pipeline: Option<RenderPipeline>,
point_light_pipeline: Option<RenderPipeline>,
@ -170,6 +171,7 @@ impl ShadowMapsPass {
transform_buffers: None,
render_meshes: None,
mesh_buffers: None,
shader: None,
pipeline: None,
point_light_pipeline: None,
@ -500,6 +502,23 @@ impl ShadowMapsPass {
queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice()));
}
/// Register all the shaders, returning the module of the
fn register_shaders(&self, graph: &mut RenderGraph) -> Result<(), wgsl_preprocessor::Error> {
let src = include_str!("../../shaders/shadows/shadows_structs.wgsl");
graph.register_shader(src)?;
let src = include_str!("../../shaders/shadows/shadows_bindings.wgsl");
graph.register_shader(src)?;
let src = include_str!("../../shaders/shadows/shadows_calc.wgsl");
graph.register_shader(src)?;
let src = include_str!("../../shaders/shadows/shadows_depth.wgsl");
graph.register_shader(src)?;
Ok(())
}
}
impl Node for ShadowMapsPass {
@ -507,6 +526,12 @@ impl Node for ShadowMapsPass {
&mut self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::NodeDesc {
self.register_shaders(graph)
.expect("failed to register shaders");
self.shader = Some(graph.preprocess_shader("lyra::shadows::depth_pass")
.expect("failed to preprocess depth shadow shaders"));
println!("{}", self.shader.as_ref().unwrap());
let mut node = NodeDesc::new(NodeType::Render, None, vec![]);
let atlas = self.atlas.get();
@ -747,8 +772,8 @@ impl Node for ShadowMapsPass {
if self.pipeline.is_none() {
let shader = Rc::new(Shader {
label: Some("shader_shadows".into()),
source: include_str!("../../shaders/shadows.wgsl").to_string(),
label: Some("lyra::shadows::depth_pass".into()),
source: self.shader.clone().unwrap(),
});
let bgl = self.bgl.clone();

View File

@ -1,6 +1,8 @@
// Vertex shader
#define_module lyra::main_3d
#import lyra::shadows::bindings::{u_light_shadow}
#import lyra::shadows::calc::{calc_shadow_dir_light, calc_shadow_point_light, calc_shadow_spot_light}
const max_light_count: u32 = 16u;
// Vertex shader
const LIGHT_TY_DIRECTIONAL = 0u;
const LIGHT_TY_POINT = 1u;
@ -22,15 +24,6 @@ struct VertexOutput {
@location(3) frag_pos_light_space: vec4<f32>,
}
struct TextureAtlasFrame {
/*offset: vec2<u32>,
size: vec2<u32>,*/
x: u32,
y: u32,
width: u32,
height: u32,
}
struct TransformData {
transform: mat4x4<f32>,
normal_matrix: mat4x4<f32>,
@ -43,7 +36,7 @@ struct CameraUniform {
projection: mat4x4<f32>,
position: vec3<f32>,
tile_debug: u32,
};
}
struct Light {
position: vec3<f32>,
@ -59,12 +52,12 @@ struct Light {
spot_cutoff: f32,
spot_outer_cutoff: f32,
light_shadow_uniform_index: array<i32, 6>,
};
}
struct Lights {
light_count: u32,
data: array<Light>,
};
}
@group(1) @binding(0)
var<uniform> u_model_transform_data: TransformData;
@ -112,47 +105,11 @@ var t_diffuse: texture_2d<f32>;
@group(0) @binding(2)
var s_diffuse: sampler;
struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame,
near_plane: f32,
far_plane: f32,
light_size_uv: f32,
light_pos: vec3<f32>,
/// boolean casted as u32
has_shadow_settings: u32,
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
constant_depth_bias: f32,
}
struct ShadowSettingsUniform {
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
}
@group(4) @binding(0)
var<storage, read_write> u_light_indices: array<u32>;
@group(4) @binding(1)
var t_light_grid: texture_storage_2d<rg32uint, read_write>; // rg32uint = vec2<u32>
@group(5) @binding(0)
var t_shadow_maps_atlas: texture_depth_2d;
@group(5) @binding(1)
var s_shadow_maps_atlas: sampler;
@group(5) @binding(2)
var s_shadow_maps_atlas_compare: sampler_comparison;
@group(5) @binding(3)
var<uniform> u_shadow_settings: ShadowSettingsUniform;
@group(5) @binding(4)
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
@group(5) @binding(5)
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
@group(5) @binding(6)
var<storage, read> u_pcf_poisson_disc_3d: array<vec3<f32>>;
@group(5) @binding(7)
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
if (u_camera.tile_debug == 1u) {
@ -173,8 +130,6 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let light_offset = tile.x;
let light_count = tile.y;
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
for (var i = 0u; i < light_count; i++) {
let light_index = u_light_indices[light_offset + i];
let light: Light = u_lights.data[light_index];
@ -187,10 +142,10 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let shadow = calc_shadow_dir_light(in.world_position, in.world_normal, light_dir, light);
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) {
let shadow = calc_shadow_point_light(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);
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) {
let shadow = calc_shadow_spot_light(in.world_position, in.world_normal, light_dir, light, atlas_dimensions);
let shadow = calc_shadow_spot_light(in.world_position, in.world_normal, light_dir, light);
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
}
}
@ -199,355 +154,6 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(light_object_res, object_color.a);
}
/// Convert 3d coords for an unwrapped cubemap to 2d coords and a side index of the cube map.
///
/// The `xy` components are the 2d coordinates in the side of the cube, and `z` is the cube
/// map side index.
///
/// Cube map index results:
/// 0 -> UNKNOWN
/// 1 -> right
/// 2 -> left
/// 3 -> top
/// 4 -> bottom
/// 5 -> near
/// 6 -> far
fn coords_to_cube_atlas(tex_coord: vec3<f32>) -> vec3<f32> {
let abs_x = abs(tex_coord.x);
let abs_y = abs(tex_coord.y);
let abs_z = abs(tex_coord.z);
var major_axis: f32 = 0.0;
var cube_idx: i32 = 0;
var res = vec2<f32>(0.0);
// Determine the dominant axis
if (abs_x >= abs_y && abs_x >= abs_z) {
major_axis = tex_coord.x;
if (tex_coord.x > 0.0) {
cube_idx = 1;
res = vec2<f32>(-tex_coord.z, -tex_coord.y);
} else {
cube_idx = 2;
res = vec2<f32>(tex_coord.z, -tex_coord.y);
}
} else if (abs_y >= abs_x && abs_y >= abs_z) {
major_axis = tex_coord.y;
if (tex_coord.y > 0.0) {
cube_idx = 3;
res = vec2<f32>(tex_coord.x, tex_coord.z);
} else {
cube_idx = 4;
res = vec2<f32>(tex_coord.x, -tex_coord.z);
}
} else {
major_axis = tex_coord.z;
if (tex_coord.z > 0.0) {
cube_idx = 5;
res = vec2<f32>(tex_coord.x, -tex_coord.y);
} else {
cube_idx = 6;
res = vec2<f32>(-tex_coord.x, -tex_coord.y);
}
}
res = (res / abs(major_axis) + 1.0) * 0.5;
res.y = 1.0 - res.y;
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(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y;
// Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// use a bias to avoid shadow acne
let current_depth = proj_coords.z - map_data.constant_depth_bias;
// get settings
let settings = get_shadow_settings(map_data);
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(map_data, xy_remapped, false);
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, map_data);
}
// only PCF
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
shadow = pcf_dir_light(xy_remapped, current_depth, map_data, texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
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);
}
// dont cast shadows outside the light's far plane
if (proj_coords.z > 1.0) {
shadow = 1.0;
}
// dont cast shadows if the texture coords would go past the shadow maps
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
shadow = 1.0;
}
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.
///
/// If `safety_offset` is true, the frame will be shrank by a tiny amount to avoid bleeding
/// into adjacent frames from fiiltering.
fn to_atlas_frame_coords(shadow_u: LightShadowMapUniform, coords: vec2<f32>, safety_offset: bool) -> 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);
// if safety_offset is true, 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 = select(0.0, (1.0 / f32(shadow_u.atlas_frame.x)) * 4.0, safety_offset);
// lerp input coords
let region_coords = vec2<f32>(
mix(region_rect.x + texel_size, region_rect.x + region_rect.z - texel_size, coords.x),
mix(region_rect.y + texel_size, region_rect.y + region_rect.w - texel_size, 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, false);
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
fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, uv_radius: f32) -> f32 {
var shadow = 0.0;
let samples_num = i32(u_shadow_settings.pcf_samples_num);
for (var i = 0; i < samples_num; i++) {
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
}
shadow /= f32(samples_num);
// clamp shadow to [0; 1]
return saturate(shadow);
}
fn calc_shadow_point_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;
let temp = coords_to_cube_atlas(normalize(frag_to_light));
var coords_2d = temp.xy;
let cube_idx = i32(temp.z);
var indices = light.light_shadow_uniform_index;
let i = indices[cube_idx - 1];
let u: LightShadowMapUniform = u_light_shadow[i];
let uniforms = array<LightShadowMapUniform, 6>(
u_light_shadow[indices[0]],
u_light_shadow[indices[1]],
u_light_shadow[indices[2]],
u_light_shadow[indices[3]],
u_light_shadow[indices[4]],
u_light_shadow[indices[5]]
);
var current_depth = length(frag_to_light);
current_depth /= u.far_plane;
current_depth -= u.constant_depth_bias;
// 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, true);
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, true);
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;
}
/// 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, true);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
}
shadow /= f32(samples_num);
// clamp shadow to [0; 1]
return saturate(shadow);
}
fn calc_shadow_spot_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light, atlas_dimensions: vec2<i32>) -> f32 {
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y;
// Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// use a bias to avoid shadow acne
let current_depth = proj_coords.z - map_data.constant_depth_bias;
// get settings
let settings = get_shadow_settings(map_data);
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(map_data, xy_remapped, false);
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
}
// only PCF is supported for spot lights
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
shadow = pcf_spot_light(xy_remapped, current_depth, map_data, i32(pcf_samples_num), texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
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);
}
// dont cast shadows outside the light's far plane
if (proj_coords.z > 1.0) {
shadow = 1.0;
}
// dont cast shadows if the texture coords would go past the shadow maps
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
shadow = 1.0;
}
return shadow;
}
/// Calculate the shadow coefficient using PCF of a directional light
fn pcf_spot_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, samples_num: i32, uv_radius: f32) -> f32 {
var shadow = 0.0;
for (var i = 0; i < samples_num; i++) {
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
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> {
let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0;
let tile_index = vec2<u32>(floor(tile_index_float));

View File

@ -0,0 +1,19 @@
#define_module lyra::shadows::bindings
#import lyra::shadows::structs::{ShadowSettingsUniform, LightShadowMapUniform}
@group(5) @binding(0)
var t_shadow_maps_atlas: texture_depth_2d;
@group(5) @binding(1)
var s_shadow_maps_atlas: sampler;
@group(5) @binding(2)
var s_shadow_maps_atlas_compare: sampler_comparison;
@group(5) @binding(3)
var<uniform> u_shadow_settings: ShadowSettingsUniform;
@group(5) @binding(4)
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
@group(5) @binding(5)
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
@group(5) @binding(6)
var<storage, read> u_pcf_poisson_disc_3d: array<vec3<f32>>;
@group(5) @binding(7)
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;

View File

@ -0,0 +1,352 @@
#define_module lyra::shadows::calc
#import lyra::shadows::structs::{ShadowSettingsUniform, LightShadowMapUniform}
#import lyra::shadows::bindings::{t_shadow_maps_atlas, s_shadow_maps_atlas, s_shadow_maps_atlas_compare, u_shadow_settings, u_light_shadow, u_pcf_poisson_disc, u_pcss_poisson_disc}
/// Convert 3d coords for an unwrapped cubemap to 2d coords and a side index of the cube map.
///
/// The `xy` components are the 2d coordinates in the side of the cube, and `z` is the cube
/// map side index.
///
/// Cube map index results:
/// 0 -> UNKNOWN
/// 1 -> right
/// 2 -> left
/// 3 -> top
/// 4 -> bottom
/// 5 -> near
/// 6 -> far
fn coords_to_cube_atlas(tex_coord: vec3<f32>) -> vec3<f32> {
let abs_x = abs(tex_coord.x);
let abs_y = abs(tex_coord.y);
let abs_z = abs(tex_coord.z);
var major_axis: f32 = 0.0;
var cube_idx: i32 = 0;
var res = vec2<f32>(0.0);
// Determine the dominant axis
if (abs_x >= abs_y && abs_x >= abs_z) {
major_axis = tex_coord.x;
if (tex_coord.x > 0.0) {
cube_idx = 1;
res = vec2<f32>(-tex_coord.z, -tex_coord.y);
} else {
cube_idx = 2;
res = vec2<f32>(tex_coord.z, -tex_coord.y);
}
} else if (abs_y >= abs_x && abs_y >= abs_z) {
major_axis = tex_coord.y;
if (tex_coord.y > 0.0) {
cube_idx = 3;
res = vec2<f32>(tex_coord.x, tex_coord.z);
} else {
cube_idx = 4;
res = vec2<f32>(tex_coord.x, -tex_coord.z);
}
} else {
major_axis = tex_coord.z;
if (tex_coord.z > 0.0) {
cube_idx = 5;
res = vec2<f32>(tex_coord.x, -tex_coord.y);
} else {
cube_idx = 6;
res = vec2<f32>(-tex_coord.x, -tex_coord.y);
}
}
res = (res / abs(major_axis) + 1.0) * 0.5;
res.y = 1.0 - res.y;
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(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y;
// Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// use a bias to avoid shadow acne
let current_depth = proj_coords.z - map_data.constant_depth_bias;
// get settings
let settings = get_shadow_settings(map_data);
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(map_data, xy_remapped, false);
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, map_data);
}
// only PCF
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
shadow = pcf_dir_light(xy_remapped, current_depth, map_data, texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
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);
}
// dont cast shadows outside the light's far plane
if (proj_coords.z > 1.0) {
shadow = 1.0;
}
// dont cast shadows if the texture coords would go past the shadow maps
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
shadow = 1.0;
}
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.
///
/// If `safety_offset` is true, the frame will be shrank by a tiny amount to avoid bleeding
/// into adjacent frames from fiiltering.
fn to_atlas_frame_coords(shadow_u: LightShadowMapUniform, coords: vec2<f32>, safety_offset: bool) -> 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);
// if safety_offset is true, 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 = select(0.0, (1.0 / f32(shadow_u.atlas_frame.x)) * 4.0, safety_offset);
// lerp input coords
let region_coords = vec2<f32>(
mix(region_rect.x + texel_size, region_rect.x + region_rect.z - texel_size, coords.x),
mix(region_rect.y + texel_size, region_rect.y + region_rect.w - texel_size, 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, false);
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
fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, uv_radius: f32) -> f32 {
var shadow = 0.0;
let samples_num = i32(u_shadow_settings.pcf_samples_num);
for (var i = 0; i < samples_num; i++) {
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
}
shadow /= f32(samples_num);
// clamp shadow to [0; 1]
return saturate(shadow);
}
fn calc_shadow_point_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
var frag_to_light = world_pos - light.position;
let temp = coords_to_cube_atlas(normalize(frag_to_light));
var coords_2d = temp.xy;
let cube_idx = i32(temp.z);
var indices = light.light_shadow_uniform_index;
let i = indices[cube_idx - 1];
let u: LightShadowMapUniform = u_light_shadow[i];
let uniforms = array<LightShadowMapUniform, 6>(
u_light_shadow[indices[0]],
u_light_shadow[indices[1]],
u_light_shadow[indices[2]],
u_light_shadow[indices[3]],
u_light_shadow[indices[4]],
u_light_shadow[indices[5]]
);
var current_depth = length(frag_to_light);
current_depth /= u.far_plane;
current_depth -= u.constant_depth_bias;
// 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, true);
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, true);
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;
}
/// 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, true);
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
}
shadow /= f32(samples_num);
// clamp shadow to [0; 1]
return saturate(shadow);
}
fn calc_shadow_spot_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y;
// Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// use a bias to avoid shadow acne
let current_depth = proj_coords.z - map_data.constant_depth_bias;
// get settings
let settings = get_shadow_settings(map_data);
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(map_data, xy_remapped, false);
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
}
// only PCF is supported for spot lights
else if pcf_samples_num > 0u {
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
shadow = pcf_spot_light(xy_remapped, current_depth, map_data, i32(pcf_samples_num), texel_size);
}
// no filtering
else {
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
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);
}
// dont cast shadows outside the light's far plane
if (proj_coords.z > 1.0) {
shadow = 1.0;
}
// dont cast shadows if the texture coords would go past the shadow maps
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
shadow = 1.0;
}
return shadow;
}
/// Calculate the shadow coefficient using PCF of a directional light
fn pcf_spot_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, samples_num: i32, uv_radius: f32) -> f32 {
var shadow = 0.0;
for (var i = 0; i < samples_num; i++) {
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
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);
}

View File

@ -1,38 +1,16 @@
#define_module lyra::shadows::depth_pass
#import lyra::shadows::structs::{LightShadowMapUniform}
struct TransformData {
transform: mat4x4<f32>,
normal_matrix: mat4x4<f32>,
}
struct TextureAtlasFrame {
offset: vec2<u32>,
size: vec2<u32>,
}
struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame,
near_plane: f32,
far_plane: f32,
light_size_uv: f32,
light_pos: vec3<f32>,
/// boolean casted as u32
has_shadow_settings: u32,
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
constant_depth_bias: f32,
}
@group(0) @binding(0)
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
/*@group(0) @binding(1)
var<uniform> u_light_pos: vec3<f32>;
@group(0) @binding(2)
var<uniform> u_light_far_plane: f32;*/
@group(1) @binding(0)
var<uniform> u_model_transform_data: TransformData;
struct VertexOutput {
@builtin(position)
clip_position: vec4<f32>,

View File

@ -0,0 +1,29 @@
#define_module lyra::shadows::structs
struct TextureAtlasFrame {
/*offset: vec2<u32>,
size: vec2<u32>,*/
x: u32,
y: u32,
width: u32,
height: u32,
}
struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame,
near_plane: f32,
far_plane: f32,
light_size_uv: f32,
light_pos: vec3<f32>,
/// boolean casted as u32
has_shadow_settings: u32,
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
constant_depth_bias: f32,
}
struct ShadowSettingsUniform {
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
}

1
wgsl-preprocessor Submodule

@ -0,0 +1 @@
Subproject commit 70daf320827f64b325a77718df07177d74d7ea58