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
This commit is contained in:
Robert Swain 2021-07-17 00:41:56 +02:00 committed by Carter Anderson
parent 30b8324672
commit 44df4c1fae
5 changed files with 54 additions and 53 deletions

View file

@ -199,7 +199,7 @@ fn setup(
},
transform: Transform {
translation: Vec3::new(0.0, 2.0, 0.0),
rotation: Quat::from_rotation_x(-1.2),
rotation: Quat::from_rotation_x(-std::f32::consts::FRAC_PI_4),
..Default::default()
},
..Default::default()

View file

@ -136,8 +136,6 @@ fn setup(
builder.spawn_bundle(PointLightBundle {
point_light: PointLight {
color: Color::WHITE,
shadow_bias_min: 0.00001,
shadow_bias_max: 0.0001,
intensity: 25.0,
..Default::default()
},
@ -150,8 +148,6 @@ fn setup(
commands.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight {
illuminance: 10000.0,
shadow_bias_min: 0.00001,
shadow_bias_max: 0.0001,
shadow_projection: OrthographicProjection {
left: -HALF_SIZE,
right: HALF_SIZE,

View file

@ -7,8 +7,8 @@ pub struct PointLight {
pub intensity: f32,
pub range: f32,
pub radius: f32,
pub shadow_bias_min: f32,
pub shadow_bias_max: f32,
pub shadow_depth_bias: f32,
pub shadow_normal_bias: f32,
}
impl Default for PointLight {
@ -18,15 +18,15 @@ impl Default for PointLight {
intensity: 200.0,
range: 20.0,
radius: 0.0,
shadow_bias_min: Self::DEFAULT_SHADOW_BIAS_MIN,
shadow_bias_max: Self::DEFAULT_SHADOW_BIAS_MAX,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
}
}
}
impl PointLight {
pub const DEFAULT_SHADOW_BIAS_MIN: f32 = 0.00005;
pub const DEFAULT_SHADOW_BIAS_MAX: f32 = 0.002;
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.02;
}
/// A Directional light.
@ -60,8 +60,8 @@ pub struct DirectionalLight {
pub color: Color,
pub illuminance: f32,
pub shadow_projection: OrthographicProjection,
pub shadow_bias_min: f32,
pub shadow_bias_max: f32,
pub shadow_depth_bias: f32,
pub shadow_normal_bias: f32,
}
impl Default for DirectionalLight {
@ -79,15 +79,15 @@ impl Default for DirectionalLight {
far: size,
..Default::default()
},
shadow_bias_min: Self::DEFAULT_SHADOW_BIAS_MIN,
shadow_bias_max: Self::DEFAULT_SHADOW_BIAS_MAX,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
}
}
}
impl DirectionalLight {
pub const DEFAULT_SHADOW_BIAS_MIN: f32 = 0.00005;
pub const DEFAULT_SHADOW_BIAS_MAX: f32 = 0.002;
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.02;
}
// Ambient light color.

View file

@ -29,8 +29,8 @@ pub struct ExtractedPointLight {
range: f32,
radius: f32,
transform: GlobalTransform,
shadow_bias_min: f32,
shadow_bias_max: f32,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
}
pub struct ExtractedDirectionalLight {
@ -38,8 +38,8 @@ pub struct ExtractedDirectionalLight {
illuminance: f32,
direction: Vec3,
projection: Mat4,
shadow_bias_min: f32,
shadow_bias_max: f32,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
}
#[repr(C)]
@ -52,8 +52,8 @@ pub struct GpuPointLight {
radius: f32,
near: f32,
far: f32,
shadow_bias_min: f32,
shadow_bias_max: f32,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
}
#[repr(C)]
@ -62,8 +62,8 @@ pub struct GpuDirectionalLight {
view_projection: Mat4,
color: Vec4,
dir_to_light: Vec3,
shadow_bias_min: f32,
shadow_bias_max: f32,
shadow_depth_bias: f32,
shadow_normal_bias: f32,
}
#[repr(C)]
@ -235,8 +235,8 @@ pub fn extract_lights(
range: point_light.range,
radius: point_light.radius,
transform: *transform,
shadow_bias_min: point_light.shadow_bias_min,
shadow_bias_max: point_light.shadow_bias_max,
shadow_depth_bias: point_light.shadow_depth_bias,
shadow_normal_bias: point_light.shadow_normal_bias,
});
}
for (entity, directional_light, transform) in directional_lights.iter() {
@ -247,8 +247,8 @@ pub fn extract_lights(
illuminance: directional_light.illuminance,
direction: transform.forward(),
projection: directional_light.shadow_projection.get_projection_matrix(),
shadow_bias_min: directional_light.shadow_bias_min,
shadow_bias_max: directional_light.shadow_bias_max,
shadow_depth_bias: directional_light.shadow_depth_bias,
shadow_normal_bias: directional_light.shadow_normal_bias,
});
}
}
@ -443,8 +443,8 @@ pub fn prepare_lights(
near: 0.1,
far: light.range,
// proj: projection,
shadow_bias_min: light.shadow_bias_min,
shadow_bias_max: light.shadow_bias_max,
shadow_depth_bias: light.shadow_depth_bias,
shadow_normal_bias: light.shadow_normal_bias,
};
}
@ -482,8 +482,8 @@ pub fn prepare_lights(
dir_to_light,
// NOTE: * view is correct, it should not be view.inverse() here
view_projection: projection * view,
shadow_bias_min: light.shadow_bias_min,
shadow_bias_max: light.shadow_bias_max,
shadow_depth_bias: light.shadow_depth_bias,
shadow_normal_bias: light.shadow_normal_bias,
};
let depth_texture_view =

View file

@ -95,16 +95,16 @@ struct PointLight {
radius: f32;
near: f32;
far: f32;
shadow_bias_min: f32;
shadow_bias_max: f32;
shadow_depth_bias: f32;
shadow_normal_bias: f32;
};
struct DirectionalLight {
view_projection: mat4x4<f32>;
color: vec4<f32>;
direction_to_light: vec3<f32>;
shadow_bias_min: f32;
shadow_bias_max: f32;
shadow_depth_bias: f32;
shadow_normal_bias: f32;
};
[[block]]
@ -379,7 +379,7 @@ fn directional_light(light: DirectionalLight, roughness: f32, NdotV: f32, normal
return (specular_light + diffuse) * light.color.rgb * NoL;
}
fn fetch_point_shadow(light_id: i32, frag_position: vec4<f32>, shadow_bias: f32) -> f32 {
fn fetch_point_shadow(light_id: i32, frag_position: vec4<f32>) -> f32 {
let light = lights.point_lights[light_id];
// 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)
// a quad (2x2 fragments) being processed not being sampled, and this messing with
// mip-mapping functionality. The shadow maps have no mipmaps so Level just samples
// from LOD 0.
let bias = 0.0001;
return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth - shadow_bias);
return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth);
}
fn fetch_directional_shadow(light_id: i32, homogeneous_coords: vec4<f32>, shadow_bias: f32) -> f32 {
fn fetch_directional_shadow(light_id: i32, frag_position: vec4<f32>) -> f32 {
let light = lights.directional_lights[light_id];
let homogeneous_coords = light.view_projection * frag_position;
if (homogeneous_coords.w <= 0.0) {
return 1.0;
}
@ -428,7 +429,7 @@ fn fetch_directional_shadow(light_id: i32, homogeneous_coords: vec4<f32>, shadow
// do the lookup, using HW PCF and comparison
// NOTE: Due to non-uniform control flow above, we must use the level variant of the texture
// sampler to avoid use of implicit derivatives causing possible undefined behavior.
return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), homogeneous_coords.z * proj_correction - shadow_bias);
return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), homogeneous_coords.z * proj_correction);
}
struct FragmentInput {
@ -521,23 +522,27 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
let n_directional_lights = i32(lights.n_directional_lights);
for (var i: i32 = 0; i < n_point_lights; i = i + 1) {
let light = lights.point_lights[i];
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
let dir_to_light = normalize(light.position.xyz - in.world_position.xyz);
let shadow_bias = max(
light.shadow_bias_max * (1.0 - dot(in.world_normal, dir_to_light)),
light.shadow_bias_min
);
let shadow = fetch_point_shadow(i, in.world_position, shadow_bias);
let depth_bias = light.shadow_depth_bias * dir_to_light.xyz;
let NdotL = dot(dir_to_light.xyz, in.world_normal.xyz);
let normal_bias = light.shadow_normal_bias * (1.0 - NdotL) * in.world_normal.xyz;
let biased_position = vec4<f32>(in.world_position.xyz + depth_bias + normal_bias, in.world_position.w);
let shadow = fetch_point_shadow(i, biased_position);
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
light_accum = light_accum + light_contrib * shadow;
}
for (var i: i32 = 0; i < n_directional_lights; i = i + 1) {
let light = lights.directional_lights[i];
let depth_bias = light.shadow_depth_bias * light.direction_to_light.xyz;
let NdotL = dot(light.direction_to_light.xyz, in.world_normal.xyz);
let normal_bias = light.shadow_normal_bias * (1.0 - NdotL) * in.world_normal.xyz;
let biased_position = vec4<f32>(in.world_position.xyz + depth_bias + normal_bias, in.world_position.w);
let shadow = fetch_directional_shadow(i, biased_position);
let light_contrib = directional_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
let shadow_bias = max(
light.shadow_bias_max * (1.0 - dot(in.world_normal, light.direction_to_light.xyz)),
light.shadow_bias_min
);
let shadow = fetch_directional_shadow(i, light.view_projection * in.world_position, shadow_bias);
light_accum = light_accum + light_contrib * shadow;
}