use glam::{Mat4, Vec3};
use std::num::NonZeroU64;
use wgpu::util::DeviceExt;
pub const SHADOW_MAP_SIZE: u32 = 2048;
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct LightUniforms {
pub view_proj: [[f32; 4]; 4],
pub light_dir: [f32; 4],
}
impl Default for LightUniforms {
fn default() -> Self {
Self {
view_proj: Mat4::IDENTITY.to_cols_array_2d(),
light_dir: [0.5, -1.0, 0.3, 0.0],
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BlurUniforms {
pub direction: [f32; 2],
pub texel_size: [f32; 2],
}
impl Default for BlurUniforms {
fn default() -> Self {
let texel_size = 1.0 / SHADOW_MAP_SIZE as f32;
Self {
direction: [1.0, 0.0],
texel_size: [texel_size, texel_size],
}
}
}
pub struct ShadowMapPass {
#[allow(dead_code)]
depth_texture: wgpu::Texture,
depth_view: wgpu::TextureView,
light_buffer: wgpu::Buffer,
comparison_sampler: wgpu::Sampler,
pub shadow_bind_group_layout: wgpu::BindGroupLayout,
shadow_bind_group: wgpu::BindGroup,
}
impl ShadowMapPass {
#[must_use]
pub fn new(device: &wgpu::Device) -> Self {
let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Shadow Map Depth"),
size: wgpu::Extent3d {
width: SHADOW_MAP_SIZE,
height: SHADOW_MAP_SIZE,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
let light_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Light Uniform Buffer"),
contents: bytemuck::cast_slice(&[LightUniforms::default()]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let comparison_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Shadow Comparison Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
compare: Some(wgpu::CompareFunction::LessEqual),
..Default::default()
});
let shadow_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Shadow Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(80),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
},
],
});
let shadow_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Shadow Bind Group"),
layout: &shadow_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: light_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&depth_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&comparison_sampler),
},
],
});
Self {
depth_texture,
depth_view,
light_buffer,
comparison_sampler,
shadow_bind_group_layout,
shadow_bind_group,
}
}
#[must_use]
pub fn compute_light_matrix(scene_center: Vec3, scene_radius: f32, light_dir: Vec3) -> Mat4 {
let light_dir = light_dir.normalize();
let light_pos = scene_center - light_dir * scene_radius * 2.0;
let up = if light_dir.y.abs() > 0.99 {
Vec3::Z
} else {
Vec3::Y
};
let view = Mat4::look_at_rh(light_pos, scene_center, up);
let proj = Mat4::orthographic_rh(
-scene_radius,
scene_radius,
-scene_radius,
scene_radius,
0.1,
scene_radius * 4.0,
);
proj * view
}
pub fn update_light(&self, queue: &wgpu::Queue, view_proj: Mat4, light_dir: Vec3) {
let uniforms = LightUniforms {
view_proj: view_proj.to_cols_array_2d(),
light_dir: [light_dir.x, light_dir.y, light_dir.z, 0.0],
};
queue.write_buffer(&self.light_buffer, 0, bytemuck::cast_slice(&[uniforms]));
}
pub fn begin_shadow_pass<'a>(
&'a self,
encoder: &'a mut wgpu::CommandEncoder,
) -> wgpu::RenderPass<'a> {
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Shadow Map Pass"),
color_attachments: &[],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
})
}
#[must_use]
pub fn depth_view(&self) -> &wgpu::TextureView {
&self.depth_view
}
#[must_use]
pub fn light_buffer(&self) -> &wgpu::Buffer {
&self.light_buffer
}
#[must_use]
pub fn comparison_sampler(&self) -> &wgpu::Sampler {
&self.comparison_sampler
}
#[must_use]
pub fn shadow_bind_group(&self) -> &wgpu::BindGroup {
&self.shadow_bind_group
}
#[must_use]
pub fn shadow_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
&self.shadow_bind_group_layout
}
}