From 927438f42bcc206f9aaaa783758e99e57cdce07c Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sat, 17 Jul 2021 00:41:56 +0200 Subject: [PATCH] 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 --- examples/3d/3d_scene_pipelined.rs | 2 +- examples/3d/cornell_box_pipelined.rs | 4 --- pipelined/bevy_pbr2/src/light.rs | 24 ++++++------- pipelined/bevy_pbr2/src/render/light.rs | 32 +++++++++--------- pipelined/bevy_pbr2/src/render/pbr.wgsl | 45 ++++++++++++++----------- 5 files changed, 54 insertions(+), 53 deletions(-) diff --git a/examples/3d/3d_scene_pipelined.rs b/examples/3d/3d_scene_pipelined.rs index b3b37efba0271..58cdcedd01fee 100644 --- a/examples/3d/3d_scene_pipelined.rs +++ b/examples/3d/3d_scene_pipelined.rs @@ -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() diff --git a/examples/3d/cornell_box_pipelined.rs b/examples/3d/cornell_box_pipelined.rs index e25be52a4669c..9dbce9a8b0e57 100644 --- a/examples/3d/cornell_box_pipelined.rs +++ b/examples/3d/cornell_box_pipelined.rs @@ -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, diff --git a/pipelined/bevy_pbr2/src/light.rs b/pipelined/bevy_pbr2/src/light.rs index 71109c906b4ac..db6d71b9237f2 100644 --- a/pipelined/bevy_pbr2/src/light.rs +++ b/pipelined/bevy_pbr2/src/light.rs @@ -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. diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index 97eb1c2d81544..158655c027e65 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -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 = diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/pipelined/bevy_pbr2/src/render/pbr.wgsl index 3bc711fb06a3a..63cf0e7711091 100644 --- a/pipelined/bevy_pbr2/src/render/pbr.wgsl +++ b/pipelined/bevy_pbr2/src/render/pbr.wgsl @@ -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; color: vec4; direction_to_light: vec3; - 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, shadow_bias: f32) -> f32 { +fn fetch_point_shadow(light_id: i32, frag_position: vec4) -> 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, 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, shadow_bias: f32) -> f32 { +fn fetch_directional_shadow(light_id: i32, frag_position: vec4) -> 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, 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 { 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(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(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; }