- Hosek-Wilkie inspired procedural sky (Rayleigh/Mie scattering, sun disk) - L2 Spherical Harmonics irradiance (9 coefficients, CPU computation) - SH evaluation in shader replaces sample_environment for diffuse IBL - GPU compute BRDF LUT (Rg16Float, higher precision than CPU Rgba8Unorm) - SkyParams (sun_direction, turbidity) in ShadowUniform Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
5.9 KiB
Rust
159 lines
5.9 KiB
Rust
use bytemuck::{Pod, Zeroable};
|
|
|
|
pub const SHADOW_MAP_SIZE: u32 = 2048;
|
|
pub const SHADOW_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
|
|
|
pub struct ShadowMap {
|
|
pub texture: wgpu::Texture,
|
|
pub view: wgpu::TextureView,
|
|
pub sampler: wgpu::Sampler,
|
|
}
|
|
|
|
impl ShadowMap {
|
|
pub fn new(device: &wgpu::Device) -> Self {
|
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
|
label: Some("Shadow Map Texture"),
|
|
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: SHADOW_FORMAT,
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
|
view_formats: &[],
|
|
});
|
|
|
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
|
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
|
label: Some("Shadow Map 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,
|
|
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
|
compare: Some(wgpu::CompareFunction::LessEqual),
|
|
..Default::default()
|
|
});
|
|
|
|
Self { texture, view, sampler }
|
|
}
|
|
|
|
pub fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
|
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
label: Some("Shadow Bind Group Layout"),
|
|
entries: &[
|
|
// binding 0: depth texture
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 0,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Texture {
|
|
sample_type: wgpu::TextureSampleType::Depth,
|
|
view_dimension: wgpu::TextureViewDimension::D2,
|
|
multisampled: false,
|
|
},
|
|
count: None,
|
|
},
|
|
// binding 1: comparison sampler
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 1,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
|
count: None,
|
|
},
|
|
// binding 2: ShadowUniform buffer
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 2,
|
|
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Buffer {
|
|
ty: wgpu::BufferBindingType::Uniform,
|
|
has_dynamic_offset: false,
|
|
min_binding_size: wgpu::BufferSize::new(
|
|
std::mem::size_of::<ShadowUniform>() as u64,
|
|
),
|
|
},
|
|
count: None,
|
|
},
|
|
// binding 3: BRDF LUT texture
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 3,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Texture {
|
|
multisampled: false,
|
|
view_dimension: wgpu::TextureViewDimension::D2,
|
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
|
},
|
|
count: None,
|
|
},
|
|
// binding 4: BRDF LUT sampler
|
|
wgpu::BindGroupLayoutEntry {
|
|
binding: 4,
|
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
|
count: None,
|
|
},
|
|
],
|
|
})
|
|
}
|
|
|
|
pub fn create_bind_group(
|
|
&self,
|
|
device: &wgpu::Device,
|
|
layout: &wgpu::BindGroupLayout,
|
|
shadow_uniform_buffer: &wgpu::Buffer,
|
|
brdf_lut_view: &wgpu::TextureView,
|
|
brdf_lut_sampler: &wgpu::Sampler,
|
|
) -> wgpu::BindGroup {
|
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
label: Some("Shadow Bind Group"),
|
|
layout,
|
|
entries: &[
|
|
wgpu::BindGroupEntry {
|
|
binding: 0,
|
|
resource: wgpu::BindingResource::TextureView(&self.view),
|
|
},
|
|
wgpu::BindGroupEntry {
|
|
binding: 1,
|
|
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
|
},
|
|
wgpu::BindGroupEntry {
|
|
binding: 2,
|
|
resource: shadow_uniform_buffer.as_entire_binding(),
|
|
},
|
|
wgpu::BindGroupEntry {
|
|
binding: 3,
|
|
resource: wgpu::BindingResource::TextureView(brdf_lut_view),
|
|
},
|
|
wgpu::BindGroupEntry {
|
|
binding: 4,
|
|
resource: wgpu::BindingResource::Sampler(brdf_lut_sampler),
|
|
},
|
|
],
|
|
})
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
|
pub struct ShadowUniform {
|
|
pub light_view_proj: [[f32; 4]; 4],
|
|
pub shadow_map_size: f32,
|
|
pub shadow_bias: f32,
|
|
pub _padding: [f32; 2],
|
|
// Sky parameters (Hosek-Wilkie inspired)
|
|
pub sun_direction: [f32; 3],
|
|
pub turbidity: f32,
|
|
// L2 Spherical Harmonics coefficients: 9 RGB coefficients packed into 7 vec4s (28 floats)
|
|
pub sh_coefficients: [[f32; 4]; 7],
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
|
pub struct ShadowPassUniform {
|
|
pub light_vp_model: [[f32; 4]; 4],
|
|
}
|