Take DirectionalLight's GlobalTransform into account when calculating shadow map volume (not just direction) (#6384)
# Objective This PR fixes #5789, by enabling movable (and scalable) directional light shadow volumes. ## Solution This PR changes `ExtractedDirectionalLight` to hold a copy of the `DirectionalLight` entity's `GlobalTransform`, instead of just a `direction` vector. This allows the shadow map volume (as defined by the light's `shadow_projection` field) to be transformed honoring translation _and_ scale transforms, and not just rotation. It also augments the texel size calculation (used to determine the `shadow_normal_bias`) so that it now takes into account the upper bound of the x/y/z scale of the `GlobalTransform`. This change makes the directional light extraction code more consistent with point and spot lights (that already use `transform`), and allows easily moving and scaling the shadow volume along with a player entity based on camera distance/angle, immediately enabling more real world use cases until we have a more sophisticated adaptive implementation, such as the one described in #3629. **Note:** While it was previously possible to update the projection achieving a similar effect, depending on the light direction and distance to the origin, the fact that the shadow map camera was always positioned at the origin with a hardcoded `Vec3::Y` up value meant you would get sub-optimal or inconsistent/incorrect results. --- ## Changelog ### Changed - `DirectionalLight` shadow volumes now honor translation and scale transforms ## Migration Guide - If your directional lights were positioned at the origin and not scaled (the default, most common scenario) no changes are needed on your part; it just works as before; - If you previously had a system for dynamically updating directional light shadow projections, you might now be able to simplify your code by updating the directional light entity's transform instead; - In the unlikely scenario that a scene with directional lights that previously rendered shadows correctly has missing shadows, make sure your directional lights are positioned at (0, 0, 0) and are not scaled to a size that's too large or too small.
This commit is contained in:
		
							parent
							
								
									1fe3589a1a
								
							
						
					
					
						commit
						1bd3d85769
					
				@ -167,6 +167,43 @@ impl Default for SpotLight {
 | 
				
			|||||||
/// | 32,000–100,000    | Direct sunlight                                |
 | 
					/// | 32,000–100,000    | Direct sunlight                                |
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
 | 
					/// 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') 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 [`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.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// 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_app::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 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)]
 | 
					#[derive(Component, Debug, Clone, Reflect)]
 | 
				
			||||||
#[reflect(Component, Default)]
 | 
					#[reflect(Component, Default)]
 | 
				
			||||||
pub struct DirectionalLight {
 | 
					pub struct DirectionalLight {
 | 
				
			||||||
@ -174,6 +211,7 @@ pub struct DirectionalLight {
 | 
				
			|||||||
    /// Illuminance in lux
 | 
					    /// Illuminance in lux
 | 
				
			||||||
    pub illuminance: f32,
 | 
					    pub illuminance: f32,
 | 
				
			||||||
    pub shadows_enabled: bool,
 | 
					    pub shadows_enabled: bool,
 | 
				
			||||||
 | 
					    /// A projection that controls the volume in which shadow maps are rendered
 | 
				
			||||||
    pub shadow_projection: OrthographicProjection,
 | 
					    pub shadow_projection: OrthographicProjection,
 | 
				
			||||||
    pub shadow_depth_bias: f32,
 | 
					    pub shadow_depth_bias: f32,
 | 
				
			||||||
    /// A bias applied along the direction of the fragment's surface normal. It is scaled to the
 | 
					    /// 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;
 | 
					    pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Controls the resolution of [`DirectionalLight`] shadow maps.
 | 
				
			||||||
#[derive(Resource, Clone, Debug, Reflect)]
 | 
					#[derive(Resource, Clone, Debug, Reflect)]
 | 
				
			||||||
#[reflect(Resource)]
 | 
					#[reflect(Resource)]
 | 
				
			||||||
pub struct DirectionalLightShadowMap {
 | 
					pub struct DirectionalLightShadowMap {
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ use bevy_ecs::{
 | 
				
			|||||||
    prelude::*,
 | 
					    prelude::*,
 | 
				
			||||||
    system::{lifetimeless::*, SystemParamItem},
 | 
					    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::{
 | 
					use bevy_render::{
 | 
				
			||||||
    camera::{Camera, CameraProjection},
 | 
					    camera::{Camera, CameraProjection},
 | 
				
			||||||
    color::Color,
 | 
					    color::Color,
 | 
				
			||||||
@ -66,7 +66,7 @@ pub struct ExtractedPointLight {
 | 
				
			|||||||
pub struct ExtractedDirectionalLight {
 | 
					pub struct ExtractedDirectionalLight {
 | 
				
			||||||
    color: Color,
 | 
					    color: Color,
 | 
				
			||||||
    illuminance: f32,
 | 
					    illuminance: f32,
 | 
				
			||||||
    direction: Vec3,
 | 
					    transform: GlobalTransform,
 | 
				
			||||||
    projection: Mat4,
 | 
					    projection: Mat4,
 | 
				
			||||||
    shadows_enabled: bool,
 | 
					    shadows_enabled: bool,
 | 
				
			||||||
    shadow_depth_bias: f32,
 | 
					    shadow_depth_bias: f32,
 | 
				
			||||||
@ -550,32 +550,27 @@ pub fn extract_lights(
 | 
				
			|||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Calulate the directional light shadow map texel size using the largest x,y dimension 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
 | 
					        // the orthographic projection divided by the shadow map resolution
 | 
				
			||||||
        // NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to:
 | 
					        // NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to:
 | 
				
			||||||
        // https://catlikecoding.com/unity/tutorials/custom-srp/directional-shadows/
 | 
					        // https://catlikecoding.com/unity/tutorials/custom-srp/directional-shadows/
 | 
				
			||||||
        let largest_dimension = (directional_light.shadow_projection.right
 | 
					        let directional_light_texel_size = transform.radius_vec3a(Vec3A::new(
 | 
				
			||||||
            - directional_light.shadow_projection.left)
 | 
					            directional_light.shadow_projection.right - directional_light.shadow_projection.left,
 | 
				
			||||||
            .max(
 | 
					            directional_light.shadow_projection.top - directional_light.shadow_projection.bottom,
 | 
				
			||||||
                directional_light.shadow_projection.top
 | 
					            0.,
 | 
				
			||||||
                    - directional_light.shadow_projection.bottom,
 | 
					        )) / directional_light_shadow_map.size as f32;
 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        let directional_light_texel_size =
 | 
					 | 
				
			||||||
            largest_dimension / directional_light_shadow_map.size as f32;
 | 
					 | 
				
			||||||
        // TODO: As above
 | 
					        // TODO: As above
 | 
				
			||||||
        let render_visible_entities = visible_entities.clone();
 | 
					        let render_visible_entities = visible_entities.clone();
 | 
				
			||||||
        commands.get_or_spawn(entity).insert((
 | 
					        commands.get_or_spawn(entity).insert((
 | 
				
			||||||
            ExtractedDirectionalLight {
 | 
					            ExtractedDirectionalLight {
 | 
				
			||||||
                color: directional_light.color,
 | 
					                color: directional_light.color,
 | 
				
			||||||
                illuminance: directional_light.illuminance,
 | 
					                illuminance: directional_light.illuminance,
 | 
				
			||||||
                direction: transform.forward(),
 | 
					                transform: *transform,
 | 
				
			||||||
                projection: directional_light.shadow_projection.get_projection_matrix(),
 | 
					                projection: directional_light.shadow_projection.get_projection_matrix(),
 | 
				
			||||||
                shadows_enabled: directional_light.shadows_enabled,
 | 
					                shadows_enabled: directional_light.shadows_enabled,
 | 
				
			||||||
                shadow_depth_bias: directional_light.shadow_depth_bias,
 | 
					                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
 | 
					                shadow_normal_bias: directional_light.shadow_normal_bias
 | 
				
			||||||
                    * directional_light_texel_size
 | 
					                    * directional_light_texel_size,
 | 
				
			||||||
                    * std::f32::consts::SQRT_2,
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            render_visible_entities,
 | 
					            render_visible_entities,
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
@ -947,7 +942,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // direction is negated to be ready for N.L
 | 
					        // 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
 | 
					        // convert from illuminance (lux) to candelas
 | 
				
			||||||
        //
 | 
					        //
 | 
				
			||||||
@ -961,9 +956,8 @@ pub fn prepare_lights(
 | 
				
			|||||||
        let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2);
 | 
					        let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2);
 | 
				
			||||||
        let intensity = light.illuminance * exposure;
 | 
					        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
 | 
					        // NOTE: For the purpose of rendering shadow maps, we apply the directional light's transform to an orthographic camera
 | 
				
			||||||
        // through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this.
 | 
					        let view = light.transform.compute_matrix().inverse();
 | 
				
			||||||
        let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y);
 | 
					 | 
				
			||||||
        // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast
 | 
					        // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast
 | 
				
			||||||
        let projection = light.projection;
 | 
					        let projection = light.projection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1175,10 +1169,6 @@ pub fn prepare_lights(
 | 
				
			|||||||
            .enumerate()
 | 
					            .enumerate()
 | 
				
			||||||
            .take(directional_shadow_maps_count)
 | 
					            .take(directional_shadow_maps_count)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // 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);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let depth_texture_view =
 | 
					            let depth_texture_view =
 | 
				
			||||||
                directional_light_depth_texture
 | 
					                directional_light_depth_texture
 | 
				
			||||||
                    .texture
 | 
					                    .texture
 | 
				
			||||||
@ -1206,7 +1196,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
                            directional_light_shadow_map.size as u32,
 | 
					                            directional_light_shadow_map.size as u32,
 | 
				
			||||||
                            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,
 | 
					                        projection: light.projection,
 | 
				
			||||||
                        hdr: false,
 | 
					                        hdr: false,
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user