HDR
This commit is contained in:
@@ -91,7 +91,7 @@ struct FrameUniforms {
|
||||
num_spot: u32,
|
||||
shadow_light_index: u32, // packed index of the shadow light ; MAX_LIGHTS = off
|
||||
light_view_proj: mat4x4<f32>, // world → shadow light clip space (Étape 14, D3)
|
||||
shadow_params: vec4<f32>, // .x = shadow map size, .y = depth bias
|
||||
shadow_params: vec4<f32>, // .x = map size, .y = constant bias, .z = slope bias
|
||||
options: vec4<u32>, // .x = unlit flag ; .y = shadows on
|
||||
};
|
||||
|
||||
@@ -202,16 +202,20 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
diffuse += frame.lights[i].color.rgb * frame.lights[i].color.a * ndotl * falloff * spot_factor;
|
||||
}
|
||||
|
||||
let lit = base * (ambient + diffuse) * compute_shadow(in.world_pos);
|
||||
let lit = base * (ambient + diffuse) * compute_shadow(in.world_pos, n);
|
||||
return vec4<f32>(lit, in.color.a);
|
||||
}
|
||||
|
||||
// Étape 14 (DRAFT 3.2, D5) : PCF shadow factor for this fragment. Reprojects the world position
|
||||
// into the shadow light's clip space, converts to depth-map UVs + normalized depth, then averages
|
||||
// a 3×3 `textureSampleCompare` neighborhood using the comparison sampler (LessEqual). Returns
|
||||
// 1.0 when fully lit (or shadows disabled), 0.0 when fully in shadow. The reference depth is
|
||||
// pulled toward the viewer by `frame.shadow_params.y` (bias) to suppress acne.
|
||||
fn compute_shadow(world_pos: vec3<f32>) -> f32 {
|
||||
// 1.0 when fully lit (or shadows disabled), 0.0 when fully in shadow.
|
||||
//
|
||||
// Bias strategy : **slope-scaled** — the reference depth is pulled toward the viewer by
|
||||
// `max(constant_bias, slope_bias * (1.0 - abs(dot(n, light_dir))))`. The slope term grows as the
|
||||
// surface becomes perpendicular to the light (grazing angle), where acne is worst. This prevents
|
||||
// the large black patches that a constant bias alone cannot suppress on large flat surfaces.
|
||||
fn compute_shadow(world_pos: vec3<f32>, normal: vec3<f32>) -> f32 {
|
||||
// Shadows off (options.y == 0) or no valid caster (sentinel = MAX_LIGHTS) → fully lit.
|
||||
if (frame.options.y == 0u || frame.shadow_light_index == MAX_LIGHTS) {
|
||||
return 1.0;
|
||||
@@ -224,9 +228,25 @@ fn compute_shadow(world_pos: vec3<f32>) -> f32 {
|
||||
// The light projection is built with the WebGPU `[0,1]` clip-depth convention (glam
|
||||
// directx/WebGPU module), so NDC z is already in [0,1]: no extra remap is needed.
|
||||
let current_depth = shadow_ndc.z;
|
||||
let bias = frame.shadow_params.y;
|
||||
let texel = 1.0 / max(frame.shadow_params.x, 1.0);
|
||||
|
||||
// Slope-scaled bias (fixes the large acne patches on surfaces at grazing angles to the light).
|
||||
// Direction from surface toward the shadow-casting light:
|
||||
// directional → position_dir.xyz (already the surface→light direction)
|
||||
// spot → normalize(light_position - world_pos)
|
||||
let sl_idx = frame.shadow_light_index;
|
||||
let sl = frame.lights[sl_idx];
|
||||
let is_dir = (sl_idx < frame.num_directional);
|
||||
var light_dir: vec3<f32>;
|
||||
if (is_dir) {
|
||||
light_dir = normalize(sl.position_dir.xyz);
|
||||
} else {
|
||||
light_dir = normalize(sl.position_dir.xyz - world_pos);
|
||||
}
|
||||
// The slope factor: 0 when the normal faces the light (no bias needed), 1 when perpendicular.
|
||||
let slope = 1.0 - abs(dot(normalize(normal), light_dir));
|
||||
let bias = max(frame.shadow_params.y, frame.shadow_params.z * slope);
|
||||
|
||||
// 3×3 PCF : average of the comparison results around the fragment's texel.
|
||||
var lit_count = 0.0;
|
||||
for (var ox = -1i; ox <= 1; ox++) {
|
||||
|
||||
Reference in New Issue
Block a user