feat(renderer): integrate shadow map sampling with 3x3 PCF into PBR shader
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ pub fn create_pbr_pipeline(
|
|||||||
camera_light_layout: &wgpu::BindGroupLayout,
|
camera_light_layout: &wgpu::BindGroupLayout,
|
||||||
texture_layout: &wgpu::BindGroupLayout,
|
texture_layout: &wgpu::BindGroupLayout,
|
||||||
material_layout: &wgpu::BindGroupLayout,
|
material_layout: &wgpu::BindGroupLayout,
|
||||||
|
shadow_layout: &wgpu::BindGroupLayout,
|
||||||
) -> wgpu::RenderPipeline {
|
) -> wgpu::RenderPipeline {
|
||||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("PBR Shader"),
|
label: Some("PBR Shader"),
|
||||||
@@ -15,7 +16,7 @@ pub fn create_pbr_pipeline(
|
|||||||
|
|
||||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("PBR Pipeline Layout"),
|
label: Some("PBR Pipeline Layout"),
|
||||||
bind_group_layouts: &[camera_light_layout, texture_layout, material_layout],
|
bind_group_layouts: &[camera_light_layout, texture_layout, material_layout, shadow_layout],
|
||||||
immediate_size: 0,
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ struct MaterialUniform {
|
|||||||
|
|
||||||
@group(2) @binding(0) var<uniform> material: MaterialUniform;
|
@group(2) @binding(0) var<uniform> material: MaterialUniform;
|
||||||
|
|
||||||
|
struct ShadowUniform {
|
||||||
|
light_view_proj: mat4x4<f32>,
|
||||||
|
shadow_map_size: f32,
|
||||||
|
shadow_bias: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(3) @binding(0) var t_shadow: texture_depth_2d;
|
||||||
|
@group(3) @binding(1) var s_shadow: sampler_comparison;
|
||||||
|
@group(3) @binding(2) var<uniform> shadow: ShadowUniform;
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) normal: vec3<f32>,
|
@location(1) normal: vec3<f32>,
|
||||||
@@ -48,6 +58,7 @@ struct VertexOutput {
|
|||||||
@location(0) world_normal: vec3<f32>,
|
@location(0) world_normal: vec3<f32>,
|
||||||
@location(1) world_pos: vec3<f32>,
|
@location(1) world_pos: vec3<f32>,
|
||||||
@location(2) uv: vec2<f32>,
|
@location(2) uv: vec2<f32>,
|
||||||
|
@location(3) light_space_pos: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
@@ -58,6 +69,7 @@ fn vs_main(model_v: VertexInput) -> VertexOutput {
|
|||||||
out.world_normal = (camera.model * vec4<f32>(model_v.normal, 0.0)).xyz;
|
out.world_normal = (camera.model * vec4<f32>(model_v.normal, 0.0)).xyz;
|
||||||
out.clip_position = camera.view_proj * world_pos;
|
out.clip_position = camera.view_proj * world_pos;
|
||||||
out.uv = model_v.uv;
|
out.uv = model_v.uv;
|
||||||
|
out.light_space_pos = shadow.light_view_proj * world_pos;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +177,44 @@ fn compute_light_contribution(
|
|||||||
return (kd * albedo / 3.14159265358979 + specular) * radiance * NdotL;
|
return (kd * albedo / 3.14159265358979 + specular) * radiance * NdotL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_shadow(light_space_pos: vec4<f32>) -> f32 {
|
||||||
|
// If shadow_map_size == 0, shadow is disabled
|
||||||
|
if shadow.shadow_map_size == 0.0 {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let proj_coords = light_space_pos.xyz / light_space_pos.w;
|
||||||
|
|
||||||
|
// wgpu NDC: x,y [-1,1], z [0,1]
|
||||||
|
let shadow_uv = vec2<f32>(
|
||||||
|
proj_coords.x * 0.5 + 0.5,
|
||||||
|
-proj_coords.y * 0.5 + 0.5,
|
||||||
|
);
|
||||||
|
let current_depth = proj_coords.z;
|
||||||
|
|
||||||
|
if shadow_uv.x < 0.0 || shadow_uv.x > 1.0 || shadow_uv.y < 0.0 || shadow_uv.y > 1.0 {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
if current_depth > 1.0 || current_depth < 0.0 {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3x3 PCF
|
||||||
|
let texel_size = 1.0 / shadow.shadow_map_size;
|
||||||
|
var shadow_val = 0.0;
|
||||||
|
for (var x = -1; x <= 1; x++) {
|
||||||
|
for (var y = -1; y <= 1; y++) {
|
||||||
|
let offset = vec2<f32>(f32(x), f32(y)) * texel_size;
|
||||||
|
shadow_val += textureSampleCompare(
|
||||||
|
t_shadow, s_shadow,
|
||||||
|
shadow_uv + offset,
|
||||||
|
current_depth - shadow.shadow_bias,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return shadow_val / 9.0;
|
||||||
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let tex_color = textureSample(t_diffuse, s_diffuse, in.uv);
|
let tex_color = textureSample(t_diffuse, s_diffuse, in.uv);
|
||||||
@@ -180,13 +230,18 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||||||
let F0 = mix(vec3<f32>(0.04, 0.04, 0.04), albedo, metallic);
|
let F0 = mix(vec3<f32>(0.04, 0.04, 0.04), albedo, metallic);
|
||||||
|
|
||||||
// Accumulate contribution from all active lights
|
// Accumulate contribution from all active lights
|
||||||
|
let shadow_factor = calculate_shadow(in.light_space_pos);
|
||||||
var Lo = vec3<f32>(0.0);
|
var Lo = vec3<f32>(0.0);
|
||||||
let light_count = min(lights_uniform.count, 16u);
|
let light_count = min(lights_uniform.count, 16u);
|
||||||
for (var i = 0u; i < light_count; i++) {
|
for (var i = 0u; i < light_count; i++) {
|
||||||
Lo += compute_light_contribution(
|
var contribution = compute_light_contribution(
|
||||||
lights_uniform.lights[i],
|
lights_uniform.lights[i],
|
||||||
N, V, in.world_pos, F0, albedo, metallic, roughness,
|
N, V, in.world_pos, F0, albedo, metallic, roughness,
|
||||||
);
|
);
|
||||||
|
if lights_uniform.lights[i].light_type == 0u {
|
||||||
|
contribution = contribution * shadow_factor;
|
||||||
|
}
|
||||||
|
Lo += contribution;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ambient term
|
// Ambient term
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ impl ShadowMap {
|
|||||||
// binding 2: ShadowUniform buffer
|
// binding 2: ShadowUniform buffer
|
||||||
wgpu::BindGroupLayoutEntry {
|
wgpu::BindGroupLayoutEntry {
|
||||||
binding: 2,
|
binding: 2,
|
||||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||||
ty: wgpu::BindingType::Buffer {
|
ty: wgpu::BindingType::Buffer {
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user