render: implement wgsl-preprocessor, split shaders
CI / build (push) Failing after 7m56s
Details
CI / build (push) Failing after 7m56s
Details
This commit is contained in:
parent
60c139f9b2
commit
2b44d7f354
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>>;
|
|
@ -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);
|
||||
}
|
|
@ -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>,
|
|
@ -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,
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 70daf320827f64b325a77718df07177d74d7ea58
|
Loading…
Reference in New Issue