From 558c6672bd8d990bc25d0ebd000567acf05f9b18 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Thu, 27 Oct 2022 01:53:19 -0300 Subject: [PATCH 1/9] Use GlobalTransform in ExtractedDirectionalLight to allow translating and scaling shadow volume --- crates/bevy_pbr/src/render/light.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index b82f4030e886c..e72d90926fa58 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -66,7 +66,7 @@ pub struct ExtractedPointLight { pub struct ExtractedDirectionalLight { color: Color, illuminance: f32, - direction: Vec3, + transform: GlobalTransform, projection: Mat4, shadows_enabled: bool, shadow_depth_bias: f32, @@ -568,7 +568,7 @@ pub fn extract_lights( ExtractedDirectionalLight { color: directional_light.color, illuminance: directional_light.illuminance, - direction: transform.forward(), + transform: *transform, projection: directional_light.shadow_projection.get_projection_matrix(), shadows_enabled: directional_light.shadows_enabled, shadow_depth_bias: directional_light.shadow_depth_bias, @@ -1103,7 +1103,7 @@ pub fn prepare_lights( .take(MAX_DIRECTIONAL_LIGHTS) { // direction is negated to be ready for N.L - let dir_to_light = -light.direction; + let dir_to_light = -light.transform.forward(); // convert from illuminance (lux) to candelas // @@ -1118,9 +1118,8 @@ pub fn prepare_lights( let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); let intensity = light.illuminance * exposure; - // NOTE: A directional light seems to have to have an eye position on the line along the direction of the light - // through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this. - let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y); + // NOTE: For the purpose of rendering shadow maps, we apply the directional light's transform to an orthographic camera + let view = light.transform.compute_matrix().inverse(); // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast let projection = light.projection; From 339546d74ea815d36736ff659e3cf7cdac5e6597 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Thu, 27 Oct 2022 01:54:56 -0300 Subject: [PATCH 2/9] Correctly account for affine transform scale upper bound when calculating texel size --- crates/bevy_pbr/src/render/light.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index e72d90926fa58..4d113bbf057a5 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, }; -use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; +use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_render::{ camera::{Camera, CameraProjection}, color::Color, @@ -560,8 +560,8 @@ pub fn extract_lights( directional_light.shadow_projection.top - directional_light.shadow_projection.bottom, ); - let directional_light_texel_size = - largest_dimension / directional_light_shadow_map.size as f32; + let directional_light_texel_size = transform.radius_vec3a(Vec3A::ONE) * largest_dimension + / directional_light_shadow_map.size as f32; // TODO: As above let render_visible_entities = visible_entities.clone(); commands.get_or_spawn(entity).insert(( From 455c2b9adf0919ff80174753b54d2a8e8a25e972 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Thu, 27 Oct 2022 22:35:02 -0300 Subject: [PATCH 3/9] Consolidate largest dimension and radius calculations for more accurate results --- crates/bevy_pbr/src/render/light.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 4d113bbf057a5..2de3631691359 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -550,18 +550,15 @@ pub fn extract_lights( continue; } - // Calulate the directional light shadow map texel size using the largest x,y dimension of + // Calulate the directional light shadow map texel size using the scaled x,y length of // the orthographic projection divided by the shadow map resolution // NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to: // https://catlikecoding.com/unity/tutorials/custom-srp/directional-shadows/ - let largest_dimension = (directional_light.shadow_projection.right - - directional_light.shadow_projection.left) - .max( - directional_light.shadow_projection.top - - directional_light.shadow_projection.bottom, - ); - let directional_light_texel_size = transform.radius_vec3a(Vec3A::ONE) * largest_dimension - / directional_light_shadow_map.size as f32; + let directional_light_texel_size = transform.radius_vec3a(Vec3A::new( + directional_light.shadow_projection.right - directional_light.shadow_projection.left, + directional_light.shadow_projection.top - directional_light.shadow_projection.bottom, + 0., + )) / directional_light_shadow_map.size as f32; // TODO: As above let render_visible_entities = visible_entities.clone(); commands.get_or_spawn(entity).insert(( From 54a76fb703a7dd54ce9474b6fce315db3d994cd8 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 28 Oct 2022 00:13:04 -0300 Subject: [PATCH 4/9] Add documentation on `DirectionalLight` shadows --- crates/bevy_pbr/src/light.rs | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 344fff88cb358..24062f7f92630 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -167,6 +167,43 @@ impl Default for SpotLight { /// | 32,000–100,000 | Direct sunlight | /// /// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux) +/// +/// ## Shadows +/// +/// To enable shadows, set the `shadows_enabled` property to `true`. +/// +/// While directional lights contribute to the illumination of meshes regardless +/// of their (or the meshes's) position, currently only a limited region of the scene +/// (the _shadow volume_) can cast and receive shadows for any given directional light. +/// +/// The shadow volume is a _rectangular cuboid_, with left/right/bottom/top/near/far +/// planes controllable via the `shadow_projection` field. It is affected by the +/// directional light entity's [`Transform`], and as such can be freely repositioned in the +/// scene, (or even scaled!) without affecting illumination in any other way, by simply +/// moving (or scaling) the entity around. The shadow volume is always oriented towards the +/// light entity's forward direction. +/// +/// For smaller scenes, a static directional light with a preset volume is typically +/// sufficient. For larger scenes with movable cameras, you might want to introduce +/// a system that dynamically repositions and scales the light entity (and therefore +/// its shadow volume) based on the scene subject's position (e.g. a player character) +/// and its relative distance to the camera. +/// +/// Shadows are produced via [shadow mapping](https://en.wikipedia.org/wiki/Shadow_mapping). +/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource: +/// +/// ``` +/// # use bevy::prelude::*; +/// use bevy::pbr::DirectionalLightShadowMap; +/// +/// App::new() +/// .insert_resource(DirectionalLightShadowMap { size: 2048 }); +/// ``` +/// +/// **Note:** Very large shadow map resolutions (> 4K) can have non-negligible performance and +/// memory impact, and not work properly under mobile or lower-end hardware. To improve the visual +/// fidelity of shadow maps, it's typically advisable to first tweak the `shadow_projection` to +/// a scene-appropriate size, before ramping up the shadow map resolution. #[derive(Component, Debug, Clone, Reflect)] #[reflect(Component, Default)] pub struct DirectionalLight { @@ -174,6 +211,7 @@ pub struct DirectionalLight { /// Illuminance in lux pub illuminance: f32, pub shadows_enabled: bool, + /// a projection that controls the volume in which shadow maps are rendered pub shadow_projection: OrthographicProjection, pub shadow_depth_bias: f32, /// A bias applied along the direction of the fragment's surface normal. It is scaled to the @@ -208,6 +246,7 @@ impl DirectionalLight { pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6; } +/// Controls the resolution of [`DirectionalLight`] shadow maps. #[derive(Resource, Clone, Debug, Reflect)] #[reflect(Resource)] pub struct DirectionalLightShadowMap { From 8b349e54c57fb035074d21b5ad5385022b432542 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 28 Oct 2022 00:28:25 -0300 Subject: [PATCH 5/9] Fix example code that's failing on CI --- crates/bevy_pbr/src/light.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 24062f7f92630..020a243b3bda1 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -193,9 +193,8 @@ impl Default for SpotLight { /// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource: /// /// ``` -/// # use bevy::prelude::*; -/// use bevy::pbr::DirectionalLightShadowMap; -/// +/// # use bevy_app::prelude::*; +/// # use bevy_pbr::DirectionalLightShadowMap; /// App::new() /// .insert_resource(DirectionalLightShadowMap { size: 2048 }); /// ``` From 5fffc35f706f6cf42a21a27aae699bc8e622e646 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Fri, 28 Oct 2022 00:29:13 -0300 Subject: [PATCH 6/9] Fix typo --- crates/bevy_pbr/src/render/light.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 2de3631691359..b5b709e91cbcc 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -550,7 +550,7 @@ pub fn extract_lights( continue; } - // Calulate the directional light shadow map texel size using the scaled x,y length of + // Calculate the directional light shadow map texel size using the scaled x,y length of // the orthographic projection divided by the shadow map resolution // NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to: // https://catlikecoding.com/unity/tutorials/custom-srp/directional-shadows/ From 5e56855550880873f15386ed9469f89b3a42612c Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 2 Nov 2022 23:15:54 -0300 Subject: [PATCH 7/9] Tweak documentation comments as per code review suggestions Co-authored-by: Robert Swain --- crates/bevy_pbr/src/light.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 020a243b3bda1..d7d3e12a59a51 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -173,12 +173,12 @@ impl Default for SpotLight { /// To enable shadows, set the `shadows_enabled` property to `true`. /// /// While directional lights contribute to the illumination of meshes regardless -/// of their (or the meshes's) position, currently only a limited region of the scene +/// of their (or the meshes') positions, currently only a limited region of the scene /// (the _shadow volume_) can cast and receive shadows for any given directional light. /// /// The shadow volume is a _rectangular cuboid_, with left/right/bottom/top/near/far /// planes controllable via the `shadow_projection` field. It is affected by the -/// directional light entity's [`Transform`], and as such can be freely repositioned in the +/// directional light entity's [`GlobalTransform`], and as such can be freely repositioned in the /// scene, (or even scaled!) without affecting illumination in any other way, by simply /// moving (or scaling) the entity around. The shadow volume is always oriented towards the /// light entity's forward direction. @@ -201,8 +201,9 @@ impl Default for SpotLight { /// /// **Note:** Very large shadow map resolutions (> 4K) can have non-negligible performance and /// memory impact, and not work properly under mobile or lower-end hardware. To improve the visual -/// fidelity of shadow maps, it's typically advisable to first tweak the `shadow_projection` to -/// a scene-appropriate size, before ramping up the shadow map resolution. +/// fidelity of shadow maps, it's typically advisable to first reduce the `shadow_projection` +/// left/right/top/bottom to a scene-appropriate size, before ramping up the shadow map +/// resolution. #[derive(Component, Debug, Clone, Reflect)] #[reflect(Component, Default)] pub struct DirectionalLight { @@ -210,7 +211,7 @@ pub struct DirectionalLight { /// Illuminance in lux pub illuminance: f32, pub shadows_enabled: bool, - /// a projection that controls the volume in which shadow maps are rendered + /// A projection that controls the volume in which shadow maps are rendered pub shadow_projection: OrthographicProjection, pub shadow_depth_bias: f32, /// A bias applied along the direction of the fragment's surface normal. It is scaled to the From 40661ce703f5ae49fc45c346a1d0feb99929b9c7 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 2 Nov 2022 23:20:49 -0300 Subject: [PATCH 8/9] Remove redundant `SQRT_2` multiplication --- crates/bevy_pbr/src/render/light.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index b5b709e91cbcc..99b55cc7be5ab 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -569,10 +569,8 @@ pub fn extract_lights( projection: directional_light.shadow_projection.get_projection_matrix(), shadows_enabled: directional_light.shadows_enabled, shadow_depth_bias: directional_light.shadow_depth_bias, - // The factor of SQRT_2 is for the worst-case diagonal offset shadow_normal_bias: directional_light.shadow_normal_bias - * directional_light_texel_size - * std::f32::consts::SQRT_2, + * directional_light_texel_size, }, render_visible_entities, )); From d404d7f8339b8846bcbf891397c03511eef1ee99 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Thu, 3 Nov 2022 22:37:10 -0300 Subject: [PATCH 9/9] Fix code to work with changes to enable multiple directional lights --- crates/bevy_pbr/src/render/light.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 7703df1656ca5..b11aecff4a6bc 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -942,7 +942,7 @@ pub fn prepare_lights( } // direction is negated to be ready for N.L - let dir_to_light = -light.direction; + let dir_to_light = light.transform.back(); // convert from illuminance (lux) to candelas // @@ -956,9 +956,8 @@ pub fn prepare_lights( let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); let intensity = light.illuminance * exposure; - // NOTE: A directional light seems to have to have an eye position on the line along the direction of the light - // through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this. - let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y); + // NOTE: For the purpose of rendering shadow maps, we apply the directional light's transform to an orthographic camera + let view = light.transform.compute_matrix().inverse(); // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast let projection = light.projection; @@ -1170,9 +1169,6 @@ pub fn prepare_lights( .enumerate() .take(directional_shadow_maps_count) { - // NOTE: For the purpose of rendering shadow maps, we apply the directional light's transform to an orthographic camera - let view = light.transform.compute_matrix().inverse(); - let depth_texture_view = directional_light_depth_texture .texture @@ -1200,7 +1196,7 @@ pub fn prepare_lights( directional_light_shadow_map.size as u32, directional_light_shadow_map.size as u32, ), - transform: GlobalTransform::from(view.inverse()), + transform: light.transform, projection: light.projection, hdr: false, },