use crate::ecs::skin::systems::GpuSkinData;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use wgpu::util::DeviceExt;
use super::cascade::{
calculate_cascade_view_projection, get_frustum_corners_world_space, reverse_z_ortho_light,
reverse_z_perspective,
};
use super::pass::ShadowDepthPass;
use super::types::{
CASCADE_ATLAS_SLOTS_PER_ROW, CASCADE_SPLIT_DISTANCES, MAX_POINT_LIGHT_SHADOWS,
MAX_SPOTLIGHT_SHADOWS, NUM_SHADOW_CASCADES, POINT_SHADOW_FACE_SIZE, POINT_SHADOW_NUM_FACES,
PointLightShadowData, PointLightShadowSlot, PointShadowUniforms, SPOTLIGHT_ATLAS_SLOTS_PER_ROW,
ShadowUniforms, SkinnedShadowObjectData, SpotlightShadowData, SpotlightShadowSlot,
};
const Y_FLIP_MATRIX: nalgebra_glm::Mat4 = nalgebra_glm::Mat4::new(
1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
);
const CUBEMAP_FACE_DIRECTIONS: [(nalgebra_glm::Vec3, nalgebra_glm::Vec3); POINT_SHADOW_NUM_FACES] = [
(
nalgebra_glm::Vec3::new(1.0, 0.0, 0.0),
nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
),
(
nalgebra_glm::Vec3::new(-1.0, 0.0, 0.0),
nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
),
(
nalgebra_glm::Vec3::new(0.0, 1.0, 0.0),
nalgebra_glm::Vec3::new(0.0, 0.0, 1.0),
),
(
nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
nalgebra_glm::Vec3::new(0.0, 0.0, -1.0),
),
(
nalgebra_glm::Vec3::new(0.0, 0.0, 1.0),
nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
),
(
nalgebra_glm::Vec3::new(0.0, 0.0, -1.0),
nalgebra_glm::Vec3::new(0.0, -1.0, 0.0),
),
];
impl PassNode<crate::ecs::world::World> for ShadowDepthPass {
fn name(&self) -> &str {
"shadow_depth_pass"
}
fn reads(&self) -> Vec<&str> {
vec![]
}
fn writes(&self) -> Vec<&str> {
vec!["shadow_depth", "spotlight_shadow_atlas"]
}
fn reads_writes(&self) -> Vec<&str> {
vec![]
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
world: &crate::ecs::world::World,
) {
self.sync_meshes(device, queue, &world.resources.mesh_cache);
let directional_light = world
.query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
.find_map(|entity| {
if let (Some(light), Some(transform)) =
(world.get_light(entity), world.get_global_transform(entity))
{
if matches!(
light.light_type,
crate::ecs::light::components::LightType::Directional
) && light.cast_shadows
{
Some((light.clone(), *transform))
} else {
None
}
} else {
None
}
});
if let Some((_light, light_transform)) = directional_light {
let light_direction = light_transform.forward_vector();
let camera_matrices = crate::ecs::camera::queries::query_active_camera_matrices(world);
if let Some(camera_matrices) = camera_matrices {
let active_camera = world.resources.active_camera;
let camera = active_camera.and_then(|entity| world.get_camera(entity));
let (fov, aspect, camera_near) = if let Some(camera) = camera {
match &camera.projection {
crate::ecs::camera::components::Projection::Perspective(persp) => {
let aspect = persp.aspect_ratio.unwrap_or_else(|| {
crate::ecs::camera::queries::query_window_aspect_ratio(world)
.unwrap_or(1.78)
});
(persp.y_fov_rad, aspect, persp.z_near)
}
crate::ecs::camera::components::Projection::Orthographic(_) => {
(std::f32::consts::FRAC_PI_4, 1.78, 0.1)
}
}
} else {
(std::f32::consts::FRAC_PI_4, 1.78, 0.1)
};
let cascade_resolution = if cfg!(target_arch = "wasm32") {
2048.0
} else {
4096.0
};
for cascade_index in 0..NUM_SHADOW_CASCADES {
let cascade_near = if cascade_index == 0 {
camera_near
} else {
CASCADE_SPLIT_DISTANCES[cascade_index - 1]
};
let cascade_far = CASCADE_SPLIT_DISTANCES[cascade_index];
let frustum_corners = get_frustum_corners_world_space(
&camera_matrices.view,
fov,
aspect,
cascade_near,
cascade_far,
);
let view_projection = calculate_cascade_view_projection(
&frustum_corners,
&light_direction,
cascade_resolution,
cascade_far,
);
self.cascade_data.view_projections[cascade_index] = view_projection.into();
}
} else {
let up = if light_direction.y.abs() > 0.99 {
nalgebra_glm::vec3(1.0, 0.0, 0.0)
} else {
nalgebra_glm::vec3(0.0, 1.0, 0.0)
};
for (cascade_index, &cascade_far) in CASCADE_SPLIT_DISTANCES.iter().enumerate() {
let half_size = cascade_far * 0.5;
let scene_center = nalgebra_glm::vec3(0.0, 0.0, 0.0);
let light_position = scene_center - light_direction * 100.0;
let light_view = nalgebra_glm::look_at(&light_position, &scene_center, &up);
let light_projection = reverse_z_ortho_light(
-half_size,
half_size,
-half_size,
half_size,
0.1,
cascade_far * 2.0,
);
self.cascade_data.view_projections[cascade_index] =
(light_projection * light_view).into();
}
}
let first_cascade_vp: nalgebra_glm::Mat4 = self.cascade_data.view_projections[0].into();
let uniforms = ShadowUniforms {
light_view_projection: first_cascade_vp.into(),
_padding: [[0.0; 4]; 4],
_padding2: [[0.0; 4]; 4],
_padding3: [[0.0; 4]; 4],
_padding4: [0.0; 4],
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
for cascade_index in 0..NUM_SHADOW_CASCADES {
let cascade_vp: nalgebra_glm::Mat4 =
self.cascade_data.view_projections[cascade_index].into();
let cascade_uniforms = ShadowUniforms {
light_view_projection: cascade_vp.into(),
_padding: [[0.0; 4]; 4],
_padding2: [[0.0; 4]; 4],
_padding3: [[0.0; 4]; 4],
_padding4: [0.0; 4],
};
queue.write_buffer(
&self.cascade_uniform_buffers[cascade_index],
0,
bytemuck::cast_slice(&[cascade_uniforms]),
);
}
}
self.shadow_caster_entities.clear();
let mut transforms = Vec::new();
for entity in world.query_entities(
crate::ecs::world::RENDER_MESH
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::CASTS_SHADOW,
) {
let render_layer = world
.get_render_layer(entity)
.map(|layer| layer.0)
.unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);
if render_layer == crate::ecs::render_layer::components::RenderLayer::OVERLAY {
continue;
}
if let (Some(render_mesh), Some(transform)) = (
world.get_render_mesh(entity),
world.get_global_transform(entity),
) && self.meshes.contains_key(&render_mesh.name)
{
let model_matrix: [[f32; 4]; 4] = transform.0.into();
transforms.push(model_matrix);
self.shadow_caster_entities.push(entity);
}
}
for entity in world.query_entities(
crate::ecs::world::INSTANCED_MESH
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::CASTS_SHADOW,
) {
let render_layer = world
.get_render_layer(entity)
.map(|layer| layer.0)
.unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);
if render_layer == crate::ecs::render_layer::components::RenderLayer::OVERLAY {
continue;
}
if let Some(visibility) = world.get_visibility(entity)
&& !visibility.visible
{
continue;
}
if let Some(instanced_mesh) = world.get_instanced_mesh(entity) {
if !self.meshes.contains_key(&instanced_mesh.mesh_name) {
continue;
}
let cached_matrices = instanced_mesh.cached_model_matrices();
for matrix in cached_matrices {
transforms.push(matrix.model);
self.shadow_caster_entities.push(entity);
}
}
}
if !transforms.is_empty() {
if transforms.len() > self.transform_buffer_size {
let new_size = (transforms.len() as f32 * 2.0).ceil() as usize;
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Transform Buffer (Resized)"),
size: (std::mem::size_of::<[[f32; 4]; 4]>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.transform_buffer = new_buffer;
self.transform_buffer_size = new_size;
self.transform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Shadow Transform Bind Group"),
layout: &self.transform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: self.transform_buffer.as_entire_binding(),
}],
});
}
queue.write_buffer(&self.transform_buffer, 0, bytemuck::cast_slice(&transforms));
}
self.sync_skinned_meshes(device, queue, &world.resources.mesh_cache);
self.skinned_shadow_caster_entities.clear();
let mut skinned_objects = Vec::new();
let skinning_data = crate::ecs::skin::systems::collect_skinning_data(world);
let mut buffers_resized = false;
let max_buffer_size = device.limits().max_buffer_size as usize;
let matrix_size = std::mem::size_of::<[[f32; 4]; 4]>();
if !skinning_data.bone_transforms.is_empty() {
let required_size = skinning_data.bone_transforms.len();
if required_size > self.bone_transforms_buffer_size {
let new_size = (required_size as f32 * 2.0).ceil() as usize;
let buffer_bytes = matrix_size * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Shadow bone transforms buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.bone_transforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Bone Transforms Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.bone_transforms_buffer_size = new_size;
buffers_resized = true;
}
}
if skinning_data.bone_transforms.len() <= self.bone_transforms_buffer_size {
queue.write_buffer(
&self.bone_transforms_buffer,
0,
bytemuck::cast_slice(&skinning_data.bone_transforms),
);
}
}
if !skinning_data.inverse_bind_matrices.is_empty() {
let required_size = skinning_data.inverse_bind_matrices.len();
if required_size > self.inverse_bind_matrices_buffer_size {
let new_size = (required_size as f32 * 2.0).ceil() as usize;
let buffer_bytes = matrix_size * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Shadow inverse bind matrices buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.inverse_bind_matrices_buffer =
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Inverse Bind Matrices Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.inverse_bind_matrices_buffer_size = new_size;
buffers_resized = true;
}
}
if skinning_data.inverse_bind_matrices.len() <= self.inverse_bind_matrices_buffer_size {
queue.write_buffer(
&self.inverse_bind_matrices_buffer,
0,
bytemuck::cast_slice(&skinning_data.inverse_bind_matrices),
);
}
}
if !skinning_data.skin_data.is_empty() {
let required_size = skinning_data.skin_data.len();
if required_size > self.skin_data_buffer_size {
let new_size = (required_size as f32 * 2.0).ceil() as usize;
let buffer_bytes = std::mem::size_of::<GpuSkinData>() * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Shadow skin data buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.skin_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skin Data Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.skin_data_buffer_size = new_size;
buffers_resized = true;
}
}
if skinning_data.skin_data.len() <= self.skin_data_buffer_size {
queue.write_buffer(
&self.skin_data_buffer,
0,
bytemuck::cast_slice(&skinning_data.skin_data),
);
}
}
let required_joints = skinning_data.total_joints as usize;
if required_joints > self.joint_matrices_buffer_size {
let new_size = (required_joints as f32 * 2.0).ceil() as usize;
let buffer_bytes = matrix_size * new_size;
if buffer_bytes > max_buffer_size {
tracing::error!(
"Shadow joint matrices buffer would exceed GPU limit: {} > {} bytes",
buffer_bytes,
max_buffer_size
);
} else {
self.joint_matrices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Joint Matrices Buffer (Resized)"),
size: buffer_bytes as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.joint_matrices_buffer_size = new_size;
buffers_resized = true;
self.rebuild_skinned_bind_group(device);
}
}
if buffers_resized {
self.rebuild_skinning_compute_bind_group(device);
}
self.total_joints_to_dispatch = skinning_data.total_joints;
for entity in world.query_entities(
crate::ecs::world::SKIN
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::CASTS_SHADOW,
) {
let render_layer = world
.get_render_layer(entity)
.map(|layer| layer.0)
.unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);
if render_layer == crate::ecs::render_layer::components::RenderLayer::OVERLAY {
continue;
}
if let Some(render_mesh) = world.get_render_mesh(entity)
&& let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
self.skinned_meshes.get(&render_mesh.name)
{
let skin_index = skinning_data
.entity_skin_indices
.get(&entity)
.copied()
.unwrap_or(0);
let joint_offset = skinning_data.get_joint_offset(skin_index);
skinned_objects.push(SkinnedShadowObjectData {
transform_index: 0,
mesh_id: (index_offset << 16) | index_count,
material_id: vertex_offset,
joint_offset,
});
self.skinned_shadow_caster_entities.push(entity);
}
}
if !skinned_objects.is_empty() {
if skinned_objects.len() > self.skinned_object_buffer_size {
let new_size = (skinned_objects.len() as f32 * 2.0).ceil() as usize;
self.skinned_object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Shadow Skinned Object Buffer (Resized)"),
size: (std::mem::size_of::<SkinnedShadowObjectData>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.skinned_object_buffer_size = new_size;
self.rebuild_skinned_bind_group(device);
}
queue.write_buffer(
&self.skinned_object_buffer,
0,
bytemuck::cast_slice(&skinned_objects),
);
}
self.spotlight_shadow_slots.clear();
self.spotlight_shadow_data.clear();
let camera_position = world
.resources
.active_camera
.and_then(|cam| world.get_global_transform(cam))
.map(|t| nalgebra_glm::vec3(t.0[(0, 3)], t.0[(1, 3)], t.0[(2, 3)]))
.unwrap_or_else(|| nalgebra_glm::vec3(0.0, 0.0, 0.0));
let mut spotlight_candidates: Vec<(
crate::ecs::world::Entity,
f32,
crate::ecs::light::components::Light,
crate::ecs::transform::components::GlobalTransform,
)> = Vec::new();
for entity in
world.query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
{
if let (Some(light), Some(transform)) =
(world.get_light(entity), world.get_global_transform(entity))
&& matches!(
light.light_type,
crate::ecs::light::components::LightType::Spot
)
&& light.cast_shadows
{
let light_pos = nalgebra_glm::vec3(
transform.0[(0, 3)],
transform.0[(1, 3)],
transform.0[(2, 3)],
);
let distance_sq = nalgebra_glm::length2(&(light_pos - camera_position));
spotlight_candidates.push((entity, distance_sq, light.clone(), *transform));
}
}
spotlight_candidates
.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let atlas_size = if cfg!(target_arch = "wasm32") {
1024
} else {
4096
};
self.spotlight_atlas_slot_size = atlas_size / SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
let slot_scale = 1.0 / SPOTLIGHT_ATLAS_SLOTS_PER_ROW as f32;
for (slot_index, (entity, _distance, light, transform)) in spotlight_candidates
.iter()
.take(MAX_SPOTLIGHT_SHADOWS)
.enumerate()
{
let light_position = nalgebra_glm::vec3(
transform.0[(0, 3)],
transform.0[(1, 3)],
transform.0[(2, 3)],
);
let light_direction = transform.forward_vector();
let up = if light_direction.y.abs() > 0.99 {
nalgebra_glm::vec3(1.0, 0.0, 0.0)
} else {
nalgebra_glm::vec3(0.0, 1.0, 0.0)
};
let target = light_position + light_direction;
let light_view = nalgebra_glm::look_at(&light_position, &target, &up);
let fov = light.outer_cone_angle * 2.0;
let near = 0.1;
let far = light.range.max(1.0);
let light_projection = reverse_z_perspective(fov, 1.0, near, far);
let view_projection = light_projection * light_view;
let slot_x = (slot_index as u32) % SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
let slot_y = (slot_index as u32) / SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
self.spotlight_shadow_slots.push(SpotlightShadowSlot {
entity: *entity,
view_projection,
slot_index: slot_index as u32,
bias: light.shadow_bias,
});
self.spotlight_shadow_data.push(SpotlightShadowData {
view_projection: view_projection.into(),
atlas_offset: [slot_x as f32 * slot_scale, slot_y as f32 * slot_scale],
atlas_scale: [slot_scale, slot_scale],
bias: light.shadow_bias,
_padding: [0.0; 3],
});
}
self.point_light_shadow_slots.clear();
self.point_light_shadow_data.clear();
let mut point_light_candidates: Vec<(
crate::ecs::world::Entity,
f32,
crate::ecs::light::components::Light,
crate::ecs::transform::components::GlobalTransform,
)> = Vec::new();
for entity in
world.query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
{
if let (Some(light), Some(transform)) =
(world.get_light(entity), world.get_global_transform(entity))
&& matches!(
light.light_type,
crate::ecs::light::components::LightType::Point
)
&& light.cast_shadows
{
let light_pos = nalgebra_glm::vec3(
transform.0[(0, 3)],
transform.0[(1, 3)],
transform.0[(2, 3)],
);
let distance_sq = nalgebra_glm::length2(&(light_pos - camera_position));
point_light_candidates.push((entity, distance_sq, light.clone(), *transform));
}
}
point_light_candidates
.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
for (slot_index, (entity, _distance, light, transform)) in point_light_candidates
.iter()
.take(MAX_POINT_LIGHT_SHADOWS)
.enumerate()
{
let light_position = nalgebra_glm::vec3(
transform.0[(0, 3)],
transform.0[(1, 3)],
transform.0[(2, 3)],
);
self.point_light_shadow_slots.push(PointLightShadowSlot {
entity: *entity,
position: light_position,
range: light.range.max(0.1),
slot_index: slot_index as u32,
bias: light.shadow_bias,
});
self.point_light_shadow_data.push(PointLightShadowData {
position: [light_position.x, light_position.y, light_position.z],
range: light.range.max(0.1),
bias: light.shadow_bias,
shadow_index: slot_index as i32,
_padding: [0.0; 2],
});
}
if !self.point_light_shadow_slots.is_empty() {
let mut all_uniforms: Vec<PointShadowUniforms> = Vec::new();
for slot in &self.point_light_shadow_slots {
let light_pos = slot.position;
let light_range = slot.range;
let near = 0.1;
let base_projection =
reverse_z_perspective(std::f32::consts::FRAC_PI_2, 1.0, near, light_range);
let projection = Y_FLIP_MATRIX * base_projection;
for (direction, up) in &CUBEMAP_FACE_DIRECTIONS {
let target = light_pos + *direction;
let view = nalgebra_glm::look_at(&light_pos, &target, up);
let view_projection = projection * view;
let mut uniform: PointShadowUniforms = bytemuck::Zeroable::zeroed();
uniform.view_projection = view_projection.into();
uniform.light_position = [light_pos.x, light_pos.y, light_pos.z];
uniform.light_range = light_range;
all_uniforms.push(uniform);
}
}
queue.write_buffer(
&self.point_light_uniform_buffer,
0,
bytemuck::cast_slice(&all_uniforms),
);
}
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
) -> crate::render::wgpu::rendergraph::Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
> {
let has_directional_shadow = context
.configs
.query_entities(crate::ecs::world::LIGHT)
.any(|entity| {
if let Some(light) = context.configs.get_light(entity) {
matches!(
light.light_type,
crate::ecs::light::components::LightType::Directional
) && light.cast_shadows
} else {
false
}
});
let has_spotlight_shadows = !self.spotlight_shadow_slots.is_empty();
let has_point_light_shadows = !self.point_light_shadow_slots.is_empty();
if !has_directional_shadow && !has_spotlight_shadows && !has_point_light_shadows {
return Ok(vec![]);
}
if self.total_joints_to_dispatch > 0 && !self.skinned_shadow_caster_entities.is_empty() {
let mut compute_pass =
context
.encoder
.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Shadow Skinning Compute Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.skinning_compute_pipeline);
compute_pass.set_bind_group(0, &self.skinning_compute_bind_group, &[]);
let num_workgroups = self.total_joints_to_dispatch.div_ceil(64);
compute_pass.dispatch_workgroups(num_workgroups, 1, 1);
}
let (shadow_depth_view, _, _) = context.get_depth_attachment("shadow_depth")?;
let (shadow_atlas_width, _) = context.get_texture_size("shadow_depth")?;
let atlas_size = shadow_atlas_width as f32;
let cascade_slot_size = atlas_size / CASCADE_ATLAS_SLOTS_PER_ROW as f32;
for cascade_index in 0..NUM_SHADOW_CASCADES {
let slot_x = (cascade_index as u32) % CASCADE_ATLAS_SLOTS_PER_ROW;
let slot_y = (cascade_index as u32) / CASCADE_ATLAS_SLOTS_PER_ROW;
let load_op = if cascade_index == 0 {
wgpu::LoadOp::Clear(0.0)
} else {
wgpu::LoadOp::Load
};
let mut shadow_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Shadow Depth Pass"),
color_attachments: &[],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: shadow_depth_view,
depth_ops: Some(wgpu::Operations {
load: load_op,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
shadow_pass.set_viewport(
slot_x as f32 * cascade_slot_size,
slot_y as f32 * cascade_slot_size,
cascade_slot_size,
cascade_slot_size,
0.0,
1.0,
);
shadow_pass.set_scissor_rect(
(slot_x as f32 * cascade_slot_size) as u32,
(slot_y as f32 * cascade_slot_size) as u32,
cascade_slot_size as u32,
cascade_slot_size as u32,
);
shadow_pass.set_pipeline(&self.pipeline);
shadow_pass.set_bind_group(0, &self.cascade_uniform_bind_groups[cascade_index], &[]);
shadow_pass.set_bind_group(1, &self.transform_bind_group, &[]);
shadow_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
shadow_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
for (instance_index, &entity) in self.shadow_caster_entities.iter().enumerate() {
let mesh_name = context
.configs
.get_render_mesh(entity)
.map(|rm| rm.name.as_str())
.or_else(|| {
context
.configs
.get_instanced_mesh(entity)
.map(|im| im.mesh_name.as_str())
});
if let Some(name) = mesh_name
&& let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
self.meshes.get(name)
{
shadow_pass.draw_indexed(
index_offset..(index_offset + index_count),
vertex_offset as i32,
instance_index as u32..(instance_index as u32 + 1),
);
}
}
if !self.skinned_shadow_caster_entities.is_empty() {
shadow_pass.set_pipeline(&self.skinned_pipeline);
shadow_pass.set_bind_group(
0,
&self.cascade_uniform_bind_groups[cascade_index],
&[],
);
shadow_pass.set_bind_group(1, &self.skinned_bind_group, &[]);
shadow_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
shadow_pass.set_index_buffer(
self.skinned_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
for (instance_index, &entity) in
self.skinned_shadow_caster_entities.iter().enumerate()
{
let mesh_name = context
.configs
.get_render_mesh(entity)
.map(|rm| rm.name.as_str());
if let Some(name) = mesh_name
&& let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
self.skinned_meshes.get(name)
{
shadow_pass.draw_indexed(
index_offset..(index_offset + index_count),
vertex_offset as i32,
instance_index as u32..(instance_index as u32 + 1),
);
}
}
}
}
if has_spotlight_shadows {
let (spotlight_atlas_view, _, _) =
context.get_depth_attachment("spotlight_shadow_atlas")?;
let (spotlight_atlas_width, _) = context.get_texture_size("spotlight_shadow_atlas")?;
let atlas_size = spotlight_atlas_width as f32;
let slot_size = atlas_size / SPOTLIGHT_ATLAS_SLOTS_PER_ROW as f32;
for slot in &self.spotlight_shadow_slots.clone() {
let slot_x = slot.slot_index % SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
let slot_y = slot.slot_index / SPOTLIGHT_ATLAS_SLOTS_PER_ROW;
let uniforms = ShadowUniforms {
light_view_projection: slot.view_projection.into(),
_padding: [[0.0; 4]; 4],
_padding2: [[0.0; 4]; 4],
_padding3: [[0.0; 4]; 4],
_padding4: [0.0; 4],
};
let slot_uniform_buffer =
context
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Spotlight Shadow Uniform Buffer"),
contents: bytemuck::cast_slice(&[uniforms]),
usage: wgpu::BufferUsages::UNIFORM,
});
let slot_uniform_bind_group =
context
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Spotlight Shadow Uniform Bind Group"),
layout: &self.uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: slot_uniform_buffer.as_entire_binding(),
}],
});
let load_op = if slot.slot_index == 0 {
wgpu::LoadOp::Clear(0.0)
} else {
wgpu::LoadOp::Load
};
let mut spotlight_pass =
context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Spotlight Shadow Pass"),
color_attachments: &[],
depth_stencil_attachment: Some(
wgpu::RenderPassDepthStencilAttachment {
view: spotlight_atlas_view,
depth_ops: Some(wgpu::Operations {
load: load_op,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
},
),
timestamp_writes: None,
occlusion_query_set: None,
});
spotlight_pass.set_viewport(
slot_x as f32 * slot_size,
slot_y as f32 * slot_size,
slot_size,
slot_size,
0.0,
1.0,
);
spotlight_pass.set_scissor_rect(
(slot_x as f32 * slot_size) as u32,
(slot_y as f32 * slot_size) as u32,
slot_size as u32,
slot_size as u32,
);
spotlight_pass.set_pipeline(&self.pipeline);
spotlight_pass.set_bind_group(0, &slot_uniform_bind_group, &[]);
spotlight_pass.set_bind_group(1, &self.transform_bind_group, &[]);
spotlight_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
spotlight_pass
.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
for (instance_index, &entity) in self.shadow_caster_entities.iter().enumerate() {
let mesh_name = context
.configs
.get_render_mesh(entity)
.map(|rm| rm.name.as_str())
.or_else(|| {
context
.configs
.get_instanced_mesh(entity)
.map(|im| im.mesh_name.as_str())
});
if let Some(name) = mesh_name
&& let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
self.meshes.get(name)
{
spotlight_pass.draw_indexed(
index_offset..(index_offset + index_count),
vertex_offset as i32,
instance_index as u32..(instance_index as u32 + 1),
);
}
}
if !self.skinned_shadow_caster_entities.is_empty() {
spotlight_pass.set_pipeline(&self.skinned_pipeline);
spotlight_pass.set_bind_group(0, &slot_uniform_bind_group, &[]);
spotlight_pass.set_bind_group(1, &self.skinned_bind_group, &[]);
spotlight_pass.set_vertex_buffer(0, self.skinned_vertex_buffer.slice(..));
spotlight_pass.set_index_buffer(
self.skinned_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
for (instance_index, &entity) in
self.skinned_shadow_caster_entities.iter().enumerate()
{
let mesh_name = context
.configs
.get_render_mesh(entity)
.map(|rm| rm.name.as_str());
if let Some(name) = mesh_name
&& let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
self.skinned_meshes.get(name)
{
spotlight_pass.draw_indexed(
index_offset..(index_offset + index_count),
vertex_offset as i32,
instance_index as u32..(instance_index as u32 + 1),
);
}
}
}
}
}
if has_point_light_shadows {
for slot in &self.point_light_shadow_slots {
for face_index in 0..POINT_SHADOW_NUM_FACES {
let uniform_index =
slot.slot_index as usize * POINT_SHADOW_NUM_FACES + face_index;
let dynamic_offset =
(uniform_index * std::mem::size_of::<PointShadowUniforms>()) as u32;
let face_view_index =
slot.slot_index as usize * POINT_SHADOW_NUM_FACES + face_index;
let face_view = &self.point_light_face_views[face_view_index];
let mut point_pass =
context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some(&format!(
"Point Light {} Face {} Shadow Pass",
slot.slot_index, face_index
)),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: face_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(
wgpu::RenderPassDepthStencilAttachment {
view: &self.point_light_depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0.0),
store: wgpu::StoreOp::Discard,
}),
stencil_ops: None,
},
),
timestamp_writes: None,
occlusion_query_set: None,
});
point_pass.set_viewport(
0.0,
0.0,
POINT_SHADOW_FACE_SIZE as f32,
POINT_SHADOW_FACE_SIZE as f32,
0.0,
1.0,
);
point_pass.set_pipeline(&self.point_light_pipeline);
point_pass.set_bind_group(
0,
&self.point_light_uniform_bind_group,
&[dynamic_offset],
);
point_pass.set_bind_group(1, &self.transform_bind_group, &[]);
point_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
point_pass
.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
for (instance_index, &entity) in self.shadow_caster_entities.iter().enumerate()
{
let mesh_name = context
.configs
.get_render_mesh(entity)
.map(|rm| rm.name.as_str())
.or_else(|| {
context
.configs
.get_instanced_mesh(entity)
.map(|im| im.mesh_name.as_str())
});
if let Some(name) = mesh_name
&& let Some(&(vertex_offset, _vertex_count, index_offset, index_count)) =
self.meshes.get(name)
{
point_pass.draw_indexed(
index_offset..(index_offset + index_count),
vertex_offset as i32,
instance_index as u32..(instance_index as u32 + 1),
);
}
}
}
}
}
Ok(vec![])
}
}