Skip to content

Commit 44df4c1

Browse files
superdumpcart
authored andcommitted
Better depth biases (#23)
* 3d_scene_pipelined: Use a shallower directional light angle to provoke acne * cornell_box_pipelined: Remove bias tweaks * bevy_pbr2: Simplify shadow biases by moving them to linear depth
1 parent 30b8324 commit 44df4c1

File tree

5 files changed

+54
-53
lines changed

5 files changed

+54
-53
lines changed

examples/3d/3d_scene_pipelined.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ fn setup(
199199
},
200200
transform: Transform {
201201
translation: Vec3::new(0.0, 2.0, 0.0),
202-
rotation: Quat::from_rotation_x(-1.2),
202+
rotation: Quat::from_rotation_x(-std::f32::consts::FRAC_PI_4),
203203
..Default::default()
204204
},
205205
..Default::default()

examples/3d/cornell_box_pipelined.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,6 @@ fn setup(
136136
builder.spawn_bundle(PointLightBundle {
137137
point_light: PointLight {
138138
color: Color::WHITE,
139-
shadow_bias_min: 0.00001,
140-
shadow_bias_max: 0.0001,
141139
intensity: 25.0,
142140
..Default::default()
143141
},
@@ -150,8 +148,6 @@ fn setup(
150148
commands.spawn_bundle(DirectionalLightBundle {
151149
directional_light: DirectionalLight {
152150
illuminance: 10000.0,
153-
shadow_bias_min: 0.00001,
154-
shadow_bias_max: 0.0001,
155151
shadow_projection: OrthographicProjection {
156152
left: -HALF_SIZE,
157153
right: HALF_SIZE,

pipelined/bevy_pbr2/src/light.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ pub struct PointLight {
77
pub intensity: f32,
88
pub range: f32,
99
pub radius: f32,
10-
pub shadow_bias_min: f32,
11-
pub shadow_bias_max: f32,
10+
pub shadow_depth_bias: f32,
11+
pub shadow_normal_bias: f32,
1212
}
1313

1414
impl Default for PointLight {
@@ -18,15 +18,15 @@ impl Default for PointLight {
1818
intensity: 200.0,
1919
range: 20.0,
2020
radius: 0.0,
21-
shadow_bias_min: Self::DEFAULT_SHADOW_BIAS_MIN,
22-
shadow_bias_max: Self::DEFAULT_SHADOW_BIAS_MAX,
21+
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
22+
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
2323
}
2424
}
2525
}
2626

2727
impl PointLight {
28-
pub const DEFAULT_SHADOW_BIAS_MIN: f32 = 0.00005;
29-
pub const DEFAULT_SHADOW_BIAS_MAX: f32 = 0.002;
28+
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
29+
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.02;
3030
}
3131

3232
/// A Directional light.
@@ -60,8 +60,8 @@ pub struct DirectionalLight {
6060
pub color: Color,
6161
pub illuminance: f32,
6262
pub shadow_projection: OrthographicProjection,
63-
pub shadow_bias_min: f32,
64-
pub shadow_bias_max: f32,
63+
pub shadow_depth_bias: f32,
64+
pub shadow_normal_bias: f32,
6565
}
6666

6767
impl Default for DirectionalLight {
@@ -79,15 +79,15 @@ impl Default for DirectionalLight {
7979
far: size,
8080
..Default::default()
8181
},
82-
shadow_bias_min: Self::DEFAULT_SHADOW_BIAS_MIN,
83-
shadow_bias_max: Self::DEFAULT_SHADOW_BIAS_MAX,
82+
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
83+
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
8484
}
8585
}
8686
}
8787

8888
impl DirectionalLight {
89-
pub const DEFAULT_SHADOW_BIAS_MIN: f32 = 0.00005;
90-
pub const DEFAULT_SHADOW_BIAS_MAX: f32 = 0.002;
89+
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
90+
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.02;
9191
}
9292

9393
// Ambient light color.

pipelined/bevy_pbr2/src/render/light.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ pub struct ExtractedPointLight {
2929
range: f32,
3030
radius: f32,
3131
transform: GlobalTransform,
32-
shadow_bias_min: f32,
33-
shadow_bias_max: f32,
32+
shadow_depth_bias: f32,
33+
shadow_normal_bias: f32,
3434
}
3535

3636
pub struct ExtractedDirectionalLight {
3737
color: Color,
3838
illuminance: f32,
3939
direction: Vec3,
4040
projection: Mat4,
41-
shadow_bias_min: f32,
42-
shadow_bias_max: f32,
41+
shadow_depth_bias: f32,
42+
shadow_normal_bias: f32,
4343
}
4444

4545
#[repr(C)]
@@ -52,8 +52,8 @@ pub struct GpuPointLight {
5252
radius: f32,
5353
near: f32,
5454
far: f32,
55-
shadow_bias_min: f32,
56-
shadow_bias_max: f32,
55+
shadow_depth_bias: f32,
56+
shadow_normal_bias: f32,
5757
}
5858

5959
#[repr(C)]
@@ -62,8 +62,8 @@ pub struct GpuDirectionalLight {
6262
view_projection: Mat4,
6363
color: Vec4,
6464
dir_to_light: Vec3,
65-
shadow_bias_min: f32,
66-
shadow_bias_max: f32,
65+
shadow_depth_bias: f32,
66+
shadow_normal_bias: f32,
6767
}
6868

6969
#[repr(C)]
@@ -235,8 +235,8 @@ pub fn extract_lights(
235235
range: point_light.range,
236236
radius: point_light.radius,
237237
transform: *transform,
238-
shadow_bias_min: point_light.shadow_bias_min,
239-
shadow_bias_max: point_light.shadow_bias_max,
238+
shadow_depth_bias: point_light.shadow_depth_bias,
239+
shadow_normal_bias: point_light.shadow_normal_bias,
240240
});
241241
}
242242
for (entity, directional_light, transform) in directional_lights.iter() {
@@ -247,8 +247,8 @@ pub fn extract_lights(
247247
illuminance: directional_light.illuminance,
248248
direction: transform.forward(),
249249
projection: directional_light.shadow_projection.get_projection_matrix(),
250-
shadow_bias_min: directional_light.shadow_bias_min,
251-
shadow_bias_max: directional_light.shadow_bias_max,
250+
shadow_depth_bias: directional_light.shadow_depth_bias,
251+
shadow_normal_bias: directional_light.shadow_normal_bias,
252252
});
253253
}
254254
}
@@ -443,8 +443,8 @@ pub fn prepare_lights(
443443
near: 0.1,
444444
far: light.range,
445445
// proj: projection,
446-
shadow_bias_min: light.shadow_bias_min,
447-
shadow_bias_max: light.shadow_bias_max,
446+
shadow_depth_bias: light.shadow_depth_bias,
447+
shadow_normal_bias: light.shadow_normal_bias,
448448
};
449449
}
450450

@@ -482,8 +482,8 @@ pub fn prepare_lights(
482482
dir_to_light,
483483
// NOTE: * view is correct, it should not be view.inverse() here
484484
view_projection: projection * view,
485-
shadow_bias_min: light.shadow_bias_min,
486-
shadow_bias_max: light.shadow_bias_max,
485+
shadow_depth_bias: light.shadow_depth_bias,
486+
shadow_normal_bias: light.shadow_normal_bias,
487487
};
488488

489489
let depth_texture_view =

pipelined/bevy_pbr2/src/render/pbr.wgsl

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,16 @@ struct PointLight {
9595
radius: f32;
9696
near: f32;
9797
far: f32;
98-
shadow_bias_min: f32;
99-
shadow_bias_max: f32;
98+
shadow_depth_bias: f32;
99+
shadow_normal_bias: f32;
100100
};
101101

102102
struct DirectionalLight {
103103
view_projection: mat4x4<f32>;
104104
color: vec4<f32>;
105105
direction_to_light: vec3<f32>;
106-
shadow_bias_min: f32;
107-
shadow_bias_max: f32;
106+
shadow_depth_bias: f32;
107+
shadow_normal_bias: f32;
108108
};
109109

110110
[[block]]
@@ -379,7 +379,7 @@ fn directional_light(light: DirectionalLight, roughness: f32, NdotV: f32, normal
379379
return (specular_light + diffuse) * light.color.rgb * NoL;
380380
}
381381

382-
fn fetch_point_shadow(light_id: i32, frag_position: vec4<f32>, shadow_bias: f32) -> f32 {
382+
fn fetch_point_shadow(light_id: i32, frag_position: vec4<f32>) -> f32 {
383383
let light = lights.point_lights[light_id];
384384

385385
// because the shadow maps align with the axes and the frustum planes are at 45 degrees
@@ -412,11 +412,12 @@ fn fetch_point_shadow(light_id: i32, frag_position: vec4<f32>, shadow_bias: f32)
412412
// a quad (2x2 fragments) being processed not being sampled, and this messing with
413413
// mip-mapping functionality. The shadow maps have no mipmaps so Level just samples
414414
// from LOD 0.
415-
let bias = 0.0001;
416-
return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth - shadow_bias);
415+
return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth);
417416
}
418417

419-
fn fetch_directional_shadow(light_id: i32, homogeneous_coords: vec4<f32>, shadow_bias: f32) -> f32 {
418+
fn fetch_directional_shadow(light_id: i32, frag_position: vec4<f32>) -> f32 {
419+
let light = lights.directional_lights[light_id];
420+
let homogeneous_coords = light.view_projection * frag_position;
420421
if (homogeneous_coords.w <= 0.0) {
421422
return 1.0;
422423
}
@@ -428,7 +429,7 @@ fn fetch_directional_shadow(light_id: i32, homogeneous_coords: vec4<f32>, shadow
428429
// do the lookup, using HW PCF and comparison
429430
// NOTE: Due to non-uniform control flow above, we must use the level variant of the texture
430431
// sampler to avoid use of implicit derivatives causing possible undefined behavior.
431-
return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), homogeneous_coords.z * proj_correction - shadow_bias);
432+
return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), homogeneous_coords.z * proj_correction);
432433
}
433434

434435
struct FragmentInput {
@@ -521,23 +522,27 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
521522
let n_directional_lights = i32(lights.n_directional_lights);
522523
for (var i: i32 = 0; i < n_point_lights; i = i + 1) {
523524
let light = lights.point_lights[i];
524-
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
525+
525526
let dir_to_light = normalize(light.position.xyz - in.world_position.xyz);
526-
let shadow_bias = max(
527-
light.shadow_bias_max * (1.0 - dot(in.world_normal, dir_to_light)),
528-
light.shadow_bias_min
529-
);
530-
let shadow = fetch_point_shadow(i, in.world_position, shadow_bias);
527+
let depth_bias = light.shadow_depth_bias * dir_to_light.xyz;
528+
let NdotL = dot(dir_to_light.xyz, in.world_normal.xyz);
529+
let normal_bias = light.shadow_normal_bias * (1.0 - NdotL) * in.world_normal.xyz;
530+
let biased_position = vec4<f32>(in.world_position.xyz + depth_bias + normal_bias, in.world_position.w);
531+
532+
let shadow = fetch_point_shadow(i, biased_position);
533+
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
531534
light_accum = light_accum + light_contrib * shadow;
532535
}
533536
for (var i: i32 = 0; i < n_directional_lights; i = i + 1) {
534537
let light = lights.directional_lights[i];
538+
539+
let depth_bias = light.shadow_depth_bias * light.direction_to_light.xyz;
540+
let NdotL = dot(light.direction_to_light.xyz, in.world_normal.xyz);
541+
let normal_bias = light.shadow_normal_bias * (1.0 - NdotL) * in.world_normal.xyz;
542+
let biased_position = vec4<f32>(in.world_position.xyz + depth_bias + normal_bias, in.world_position.w);
543+
544+
let shadow = fetch_directional_shadow(i, biased_position);
535545
let light_contrib = directional_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
536-
let shadow_bias = max(
537-
light.shadow_bias_max * (1.0 - dot(in.world_normal, light.direction_to_light.xyz)),
538-
light.shadow_bias_min
539-
);
540-
let shadow = fetch_directional_shadow(i, light.view_projection * in.world_position, shadow_bias);
541546
light_accum = light_accum + light_contrib * shadow;
542547
}
543548

0 commit comments

Comments
 (0)