Cascaded shadow maps. (#7064)
Co-authored-by: Robert Swain <robert.swain@gmail.com> # Objective Implements cascaded shadow maps for directional lights, which produces better quality shadows without needing excessively large shadow maps. Fixes #3629 Before  After  ## Solution Rather than rendering a single shadow map for directional light, the view frustum is divided into a series of cascades, each of which gets its own shadow map. The correct cascade is then sampled for shadow determination. --- ## Changelog Directional lights now use cascaded shadow maps for improved shadow quality. ## Migration Guide You no longer have to manually specify a `shadow_projection` for a directional light, and these settings should be removed. If customization of how cascaded shadow maps work is desired, modify the `CascadeShadowConfig` component instead.
This commit is contained in:
		
							parent
							
								
									f27e9b241d
								
							
						
					
					
						commit
						c3a46822e1
					
				@ -61,7 +61,7 @@ impl Camera2dBundle {
 | 
				
			|||||||
        let transform = Transform::from_xyz(0.0, 0.0, far - 0.1);
 | 
					        let transform = Transform::from_xyz(0.0, 0.0, far - 0.1);
 | 
				
			||||||
        let view_projection =
 | 
					        let view_projection =
 | 
				
			||||||
            projection.get_projection_matrix() * transform.compute_matrix().inverse();
 | 
					            projection.get_projection_matrix() * transform.compute_matrix().inverse();
 | 
				
			||||||
        let frustum = Frustum::from_view_projection(
 | 
					        let frustum = Frustum::from_view_projection_custom_far(
 | 
				
			||||||
            &view_projection,
 | 
					            &view_projection,
 | 
				
			||||||
            &transform.translation,
 | 
					            &transform.translation,
 | 
				
			||||||
            &transform.back(),
 | 
					            &transform.back(),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,17 @@
 | 
				
			|||||||
use crate::{DirectionalLight, Material, PointLight, SpotLight, StandardMaterial};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    CascadeShadowConfig, Cascades, DirectionalLight, Material, PointLight, SpotLight,
 | 
				
			||||||
 | 
					    StandardMaterial,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use bevy_asset::Handle;
 | 
					use bevy_asset::Handle;
 | 
				
			||||||
use bevy_ecs::{bundle::Bundle, component::Component, reflect::ReflectComponent};
 | 
					use bevy_ecs::{bundle::Bundle, component::Component, prelude::Entity, reflect::ReflectComponent};
 | 
				
			||||||
use bevy_reflect::Reflect;
 | 
					use bevy_reflect::Reflect;
 | 
				
			||||||
use bevy_render::{
 | 
					use bevy_render::{
 | 
				
			||||||
    mesh::Mesh,
 | 
					    mesh::Mesh,
 | 
				
			||||||
    primitives::{CubemapFrusta, Frustum},
 | 
					    primitives::{CascadesFrusta, CubemapFrusta, Frustum},
 | 
				
			||||||
    view::{ComputedVisibility, Visibility, VisibleEntities},
 | 
					    view::{ComputedVisibility, Visibility, VisibleEntities},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use bevy_transform::components::{GlobalTransform, Transform};
 | 
					use bevy_transform::components::{GlobalTransform, Transform};
 | 
				
			||||||
 | 
					use bevy_utils::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`].
 | 
					/// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`].
 | 
				
			||||||
pub type PbrBundle = MaterialMeshBundle<StandardMaterial>;
 | 
					pub type PbrBundle = MaterialMeshBundle<StandardMaterial>;
 | 
				
			||||||
@ -63,6 +67,14 @@ impl CubemapVisibleEntities {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Clone, Debug, Default, Reflect)]
 | 
				
			||||||
 | 
					#[reflect(Component)]
 | 
				
			||||||
 | 
					pub struct CascadesVisibleEntities {
 | 
				
			||||||
 | 
					    /// Map of view entity to the visible entities for each cascade frustum.
 | 
				
			||||||
 | 
					    #[reflect(ignore)]
 | 
				
			||||||
 | 
					    pub entities: HashMap<Entity, Vec<VisibleEntities>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A component bundle for [`PointLight`] entities.
 | 
					/// A component bundle for [`PointLight`] entities.
 | 
				
			||||||
#[derive(Debug, Bundle, Default)]
 | 
					#[derive(Debug, Bundle, Default)]
 | 
				
			||||||
pub struct PointLightBundle {
 | 
					pub struct PointLightBundle {
 | 
				
			||||||
@ -95,8 +107,10 @@ pub struct SpotLightBundle {
 | 
				
			|||||||
#[derive(Debug, Bundle, Default)]
 | 
					#[derive(Debug, Bundle, Default)]
 | 
				
			||||||
pub struct DirectionalLightBundle {
 | 
					pub struct DirectionalLightBundle {
 | 
				
			||||||
    pub directional_light: DirectionalLight,
 | 
					    pub directional_light: DirectionalLight,
 | 
				
			||||||
    pub frustum: Frustum,
 | 
					    pub frusta: CascadesFrusta,
 | 
				
			||||||
    pub visible_entities: VisibleEntities,
 | 
					    pub cascades: Cascades,
 | 
				
			||||||
 | 
					    pub cascade_shadow_config: CascadeShadowConfig,
 | 
				
			||||||
 | 
					    pub visible_entities: CascadesVisibleEntities,
 | 
				
			||||||
    pub transform: Transform,
 | 
					    pub transform: Transform,
 | 
				
			||||||
    pub global_transform: GlobalTransform,
 | 
					    pub global_transform: GlobalTransform,
 | 
				
			||||||
    /// Enables or disables the light
 | 
					    /// Enables or disables the light
 | 
				
			||||||
 | 
				
			|||||||
@ -145,17 +145,20 @@ impl Plugin for PbrPlugin {
 | 
				
			|||||||
            Shader::from_wgsl
 | 
					            Shader::from_wgsl
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        app.register_type::<CubemapVisibleEntities>()
 | 
					        app.register_asset_reflect::<StandardMaterial>()
 | 
				
			||||||
            .register_type::<DirectionalLight>()
 | 
					 | 
				
			||||||
            .register_type::<PointLight>()
 | 
					 | 
				
			||||||
            .register_type::<SpotLight>()
 | 
					 | 
				
			||||||
            .register_asset_reflect::<StandardMaterial>()
 | 
					 | 
				
			||||||
            .register_type::<AmbientLight>()
 | 
					            .register_type::<AmbientLight>()
 | 
				
			||||||
            .register_type::<DirectionalLightShadowMap>()
 | 
					            .register_type::<CascadeShadowConfig>()
 | 
				
			||||||
 | 
					            .register_type::<Cascades>()
 | 
				
			||||||
 | 
					            .register_type::<CascadesVisibleEntities>()
 | 
				
			||||||
            .register_type::<ClusterConfig>()
 | 
					            .register_type::<ClusterConfig>()
 | 
				
			||||||
            .register_type::<ClusterZConfig>()
 | 
					 | 
				
			||||||
            .register_type::<ClusterFarZMode>()
 | 
					            .register_type::<ClusterFarZMode>()
 | 
				
			||||||
 | 
					            .register_type::<ClusterZConfig>()
 | 
				
			||||||
 | 
					            .register_type::<CubemapVisibleEntities>()
 | 
				
			||||||
 | 
					            .register_type::<DirectionalLight>()
 | 
				
			||||||
 | 
					            .register_type::<DirectionalLightShadowMap>()
 | 
				
			||||||
 | 
					            .register_type::<PointLight>()
 | 
				
			||||||
            .register_type::<PointLightShadowMap>()
 | 
					            .register_type::<PointLightShadowMap>()
 | 
				
			||||||
 | 
					            .register_type::<SpotLight>()
 | 
				
			||||||
            .add_plugin(MeshRenderPlugin)
 | 
					            .add_plugin(MeshRenderPlugin)
 | 
				
			||||||
            .add_plugin(MaterialPlugin::<StandardMaterial> {
 | 
					            .add_plugin(MaterialPlugin::<StandardMaterial> {
 | 
				
			||||||
                prepass_enabled: self.prepass_enabled,
 | 
					                prepass_enabled: self.prepass_enabled,
 | 
				
			||||||
@ -183,6 +186,12 @@ impl Plugin for PbrPlugin {
 | 
				
			|||||||
                    .after(CameraUpdateSystem)
 | 
					                    .after(CameraUpdateSystem)
 | 
				
			||||||
                    .after(ModifiesWindows),
 | 
					                    .after(ModifiesWindows),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .add_system_to_stage(
 | 
				
			||||||
 | 
					                CoreStage::PostUpdate,
 | 
				
			||||||
 | 
					                update_directional_light_cascades
 | 
				
			||||||
 | 
					                    .label(SimulationLightSystems::UpdateDirectionalLightCascades)
 | 
				
			||||||
 | 
					                    .after(TransformSystem::TransformPropagate),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            .add_system_to_stage(
 | 
					            .add_system_to_stage(
 | 
				
			||||||
                CoreStage::PostUpdate,
 | 
					                CoreStage::PostUpdate,
 | 
				
			||||||
                update_directional_light_frusta
 | 
					                update_directional_light_frusta
 | 
				
			||||||
@ -190,6 +199,7 @@ impl Plugin for PbrPlugin {
 | 
				
			|||||||
                    // This must run after CheckVisibility because it relies on ComputedVisibility::is_visible()
 | 
					                    // This must run after CheckVisibility because it relies on ComputedVisibility::is_visible()
 | 
				
			||||||
                    .after(VisibilitySystems::CheckVisibility)
 | 
					                    .after(VisibilitySystems::CheckVisibility)
 | 
				
			||||||
                    .after(TransformSystem::TransformPropagate)
 | 
					                    .after(TransformSystem::TransformPropagate)
 | 
				
			||||||
 | 
					                    .after(SimulationLightSystems::UpdateDirectionalLightCascades)
 | 
				
			||||||
                    // We assume that no entity will be both a directional light and a spot light,
 | 
					                    // We assume that no entity will be both a directional light and a spot light,
 | 
				
			||||||
                    // so these systems will run independently of one another.
 | 
					                    // so these systems will run independently of one another.
 | 
				
			||||||
                    // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
 | 
					                    // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
 | 
				
			||||||
 | 
				
			|||||||
@ -4,21 +4,23 @@ use bevy_ecs::prelude::*;
 | 
				
			|||||||
use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
 | 
					use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
 | 
				
			||||||
use bevy_reflect::prelude::*;
 | 
					use bevy_reflect::prelude::*;
 | 
				
			||||||
use bevy_render::{
 | 
					use bevy_render::{
 | 
				
			||||||
    camera::{Camera, CameraProjection, OrthographicProjection},
 | 
					    camera::Camera,
 | 
				
			||||||
    color::Color,
 | 
					    color::Color,
 | 
				
			||||||
    extract_resource::ExtractResource,
 | 
					    extract_resource::ExtractResource,
 | 
				
			||||||
    primitives::{Aabb, CubemapFrusta, Frustum, Plane, Sphere},
 | 
					    prelude::Projection,
 | 
				
			||||||
 | 
					    primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Plane, Sphere},
 | 
				
			||||||
    render_resource::BufferBindingType,
 | 
					    render_resource::BufferBindingType,
 | 
				
			||||||
    renderer::RenderDevice,
 | 
					    renderer::RenderDevice,
 | 
				
			||||||
    view::{ComputedVisibility, RenderLayers, VisibleEntities},
 | 
					    view::{ComputedVisibility, RenderLayers, VisibleEntities},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use bevy_transform::{components::GlobalTransform, prelude::Transform};
 | 
					use bevy_transform::{components::GlobalTransform, prelude::Transform};
 | 
				
			||||||
use bevy_utils::tracing::warn;
 | 
					use bevy_utils::{tracing::warn, HashMap};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    calculate_cluster_factors, spot_light_projection_matrix, spot_light_view_matrix, CubeMapFace,
 | 
					    calculate_cluster_factors, spot_light_projection_matrix, spot_light_view_matrix,
 | 
				
			||||||
    CubemapVisibleEntities, ViewClusterBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
 | 
					    CascadesVisibleEntities, CubeMapFace, CubemapVisibleEntities, ViewClusterBindings,
 | 
				
			||||||
    CUBE_MAP_FACES, MAX_UNIFORM_BUFFER_POINT_LIGHTS, POINT_LIGHT_NEAR_Z,
 | 
					    CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, CUBE_MAP_FACES, MAX_UNIFORM_BUFFER_POINT_LIGHTS,
 | 
				
			||||||
 | 
					    POINT_LIGHT_NEAR_Z,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A light that emits light in all directions from a central point.
 | 
					/// A light that emits light in all directions from a central point.
 | 
				
			||||||
@ -172,24 +174,11 @@ impl Default for SpotLight {
 | 
				
			|||||||
///
 | 
					///
 | 
				
			||||||
/// To enable shadows, set the `shadows_enabled` property to `true`.
 | 
					/// To enable shadows, set the `shadows_enabled` property to `true`.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// While directional lights contribute to the illumination of meshes regardless
 | 
					/// Shadows are produced via [cascaded shadow maps](https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf).
 | 
				
			||||||
/// 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
 | 
					/// To modify the cascade set up, such as the number of cascades or the maximum shadow distance,
 | 
				
			||||||
/// planes controllable via the `shadow_projection` field. It is affected by the
 | 
					/// change the [`CascadeShadowConfig`] component of the [`crate::bundle::DirectionalLightBundle`].
 | 
				
			||||||
/// 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:
 | 
					/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource:
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
@ -198,12 +187,6 @@ impl Default for SpotLight {
 | 
				
			|||||||
/// App::new()
 | 
					/// App::new()
 | 
				
			||||||
///     .insert_resource(DirectionalLightShadowMap { size: 2048 });
 | 
					///     .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 {
 | 
				
			||||||
@ -211,8 +194,6 @@ 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_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
 | 
				
			||||||
    /// shadow map's texel size so that it is automatically adjusted to the orthographic projection.
 | 
					    /// shadow map's texel size so that it is automatically adjusted to the orthographic projection.
 | 
				
			||||||
@ -221,20 +202,10 @@ pub struct DirectionalLight {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl Default for DirectionalLight {
 | 
					impl Default for DirectionalLight {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        let size = 100.0;
 | 
					 | 
				
			||||||
        DirectionalLight {
 | 
					        DirectionalLight {
 | 
				
			||||||
            color: Color::rgb(1.0, 1.0, 1.0),
 | 
					            color: Color::rgb(1.0, 1.0, 1.0),
 | 
				
			||||||
            illuminance: 100000.0,
 | 
					            illuminance: 100000.0,
 | 
				
			||||||
            shadows_enabled: false,
 | 
					            shadows_enabled: false,
 | 
				
			||||||
            shadow_projection: OrthographicProjection {
 | 
					 | 
				
			||||||
                left: -size,
 | 
					 | 
				
			||||||
                right: size,
 | 
					 | 
				
			||||||
                bottom: -size,
 | 
					 | 
				
			||||||
                top: size,
 | 
					 | 
				
			||||||
                near: -size,
 | 
					 | 
				
			||||||
                far: size,
 | 
					 | 
				
			||||||
                ..Default::default()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
 | 
					            shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
 | 
				
			||||||
            shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
 | 
					            shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -256,9 +227,258 @@ pub struct DirectionalLightShadowMap {
 | 
				
			|||||||
impl Default for DirectionalLightShadowMap {
 | 
					impl Default for DirectionalLightShadowMap {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        #[cfg(feature = "webgl")]
 | 
					        #[cfg(feature = "webgl")]
 | 
				
			||||||
        return Self { size: 2048 };
 | 
					        return Self { size: 1024 };
 | 
				
			||||||
        #[cfg(not(feature = "webgl"))]
 | 
					        #[cfg(not(feature = "webgl"))]
 | 
				
			||||||
        return Self { size: 4096 };
 | 
					        return Self { size: 2048 };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Controls how cascaded shadow mapping works.
 | 
				
			||||||
 | 
					#[derive(Component, Clone, Debug, Reflect)]
 | 
				
			||||||
 | 
					#[reflect(Component)]
 | 
				
			||||||
 | 
					pub struct CascadeShadowConfig {
 | 
				
			||||||
 | 
					    /// The (positive) distance to the far boundary of each cascade.
 | 
				
			||||||
 | 
					    pub bounds: Vec<f32>,
 | 
				
			||||||
 | 
					    /// The proportion of overlap each cascade has with the previous cascade.
 | 
				
			||||||
 | 
					    pub overlap_proportion: f32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for CascadeShadowConfig {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        if cfg!(feature = "webgl") {
 | 
				
			||||||
 | 
					            // Currently only support one cascade in webgl.
 | 
				
			||||||
 | 
					            Self::new(1, 5.0, 100.0, 0.2)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Self::new(4, 5.0, 1000.0, 0.2)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn calculate_cascade_bounds(
 | 
				
			||||||
 | 
					    num_cascades: usize,
 | 
				
			||||||
 | 
					    nearest_bound: f32,
 | 
				
			||||||
 | 
					    shadow_maximum_distance: f32,
 | 
				
			||||||
 | 
					) -> Vec<f32> {
 | 
				
			||||||
 | 
					    if num_cascades == 1 {
 | 
				
			||||||
 | 
					        return vec![shadow_maximum_distance];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let base = (shadow_maximum_distance / nearest_bound).powf(1.0 / (num_cascades - 1) as f32);
 | 
				
			||||||
 | 
					    (0..num_cascades)
 | 
				
			||||||
 | 
					        .map(|i| nearest_bound * base.powf(i as f32))
 | 
				
			||||||
 | 
					        .collect()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl CascadeShadowConfig {
 | 
				
			||||||
 | 
					    /// Returns a cascade config for `num_cascades` cascades, with the first cascade
 | 
				
			||||||
 | 
					    /// having far bound `nearest_bound` and the last cascade having far bound `shadow_maximum_distance`.
 | 
				
			||||||
 | 
					    /// In-between cascades will be exponentially spaced.
 | 
				
			||||||
 | 
					    pub fn new(
 | 
				
			||||||
 | 
					        num_cascades: usize,
 | 
				
			||||||
 | 
					        nearest_bound: f32,
 | 
				
			||||||
 | 
					        shadow_maximum_distance: f32,
 | 
				
			||||||
 | 
					        overlap_proportion: f32,
 | 
				
			||||||
 | 
					    ) -> Self {
 | 
				
			||||||
 | 
					        assert!(
 | 
				
			||||||
 | 
					            num_cascades > 0,
 | 
				
			||||||
 | 
					            "num_cascades must be positive, but was {}",
 | 
				
			||||||
 | 
					            num_cascades
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        assert!(
 | 
				
			||||||
 | 
					            (0.0..1.0).contains(&overlap_proportion),
 | 
				
			||||||
 | 
					            "overlap_proportion must be in [0.0, 1.0) but was {}",
 | 
				
			||||||
 | 
					            overlap_proportion
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            bounds: calculate_cascade_bounds(num_cascades, nearest_bound, shadow_maximum_distance),
 | 
				
			||||||
 | 
					            overlap_proportion,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Clone, Debug, Default, Reflect)]
 | 
				
			||||||
 | 
					#[reflect(Component)]
 | 
				
			||||||
 | 
					pub struct Cascades {
 | 
				
			||||||
 | 
					    /// Map from a view to the configuration of each of its [`Cascade`]s.
 | 
				
			||||||
 | 
					    pub(crate) cascades: HashMap<Entity, Vec<Cascade>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, Default, Reflect, FromReflect)]
 | 
				
			||||||
 | 
					pub struct Cascade {
 | 
				
			||||||
 | 
					    /// The transform of the light, i.e. the view to world matrix.
 | 
				
			||||||
 | 
					    pub(crate) view_transform: Mat4,
 | 
				
			||||||
 | 
					    /// The orthographic projection for this cascade.
 | 
				
			||||||
 | 
					    pub(crate) projection: Mat4,
 | 
				
			||||||
 | 
					    /// The view-projection matrix for this cacade, converting world space into light clip space.
 | 
				
			||||||
 | 
					    /// Importantly, this is derived and stored separately from `view_transform` and `projection` to
 | 
				
			||||||
 | 
					    /// ensure shadow stability.
 | 
				
			||||||
 | 
					    pub(crate) view_projection: Mat4,
 | 
				
			||||||
 | 
					    /// Size of each shadow map texel in world units.
 | 
				
			||||||
 | 
					    pub(crate) texel_size: f32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn update_directional_light_cascades(
 | 
				
			||||||
 | 
					    directional_light_shadow_map: Res<DirectionalLightShadowMap>,
 | 
				
			||||||
 | 
					    views: Query<(Entity, &GlobalTransform, &Projection, &Camera)>,
 | 
				
			||||||
 | 
					    mut lights: Query<(
 | 
				
			||||||
 | 
					        &GlobalTransform,
 | 
				
			||||||
 | 
					        &DirectionalLight,
 | 
				
			||||||
 | 
					        &CascadeShadowConfig,
 | 
				
			||||||
 | 
					        &mut Cascades,
 | 
				
			||||||
 | 
					    )>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    let views = views
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .filter_map(|view| match view {
 | 
				
			||||||
 | 
					            // TODO: orthographic camera projection support.
 | 
				
			||||||
 | 
					            (entity, transform, Projection::Perspective(projection), camera)
 | 
				
			||||||
 | 
					                if camera.is_active =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Some((
 | 
				
			||||||
 | 
					                    entity,
 | 
				
			||||||
 | 
					                    projection.aspect_ratio,
 | 
				
			||||||
 | 
					                    (0.5 * projection.fov).tan(),
 | 
				
			||||||
 | 
					                    transform.compute_matrix(),
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .collect::<Vec<_>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (transform, directional_light, cascades_config, mut cascades) in lights.iter_mut() {
 | 
				
			||||||
 | 
					        if !directional_light.shadows_enabled {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // It is very important to the numerical and thus visual stability of shadows that
 | 
				
			||||||
 | 
					        // light_to_world has orthogonal upper-left 3x3 and zero translation.
 | 
				
			||||||
 | 
					        // Even though only the direction (i.e. rotation) of the light matters, we don't constrain
 | 
				
			||||||
 | 
					        // users to not change any other aspects of the transform - there's no guarantee
 | 
				
			||||||
 | 
					        // `transform.compute_matrix()` will give us a matrix with our desired properties.
 | 
				
			||||||
 | 
					        // Instead, we directly create a good matrix from just the rotation.
 | 
				
			||||||
 | 
					        let light_to_world = Mat4::from_quat(transform.compute_transform().rotation);
 | 
				
			||||||
 | 
					        let light_to_world_inverse = light_to_world.inverse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cascades.cascades.clear();
 | 
				
			||||||
 | 
					        for (view_entity, aspect_ratio, tan_half_fov, view_to_world) in views.iter().copied() {
 | 
				
			||||||
 | 
					            let camera_to_light_view = light_to_world_inverse * view_to_world;
 | 
				
			||||||
 | 
					            let view_cascades = cascades_config
 | 
				
			||||||
 | 
					                .bounds
 | 
				
			||||||
 | 
					                .iter()
 | 
				
			||||||
 | 
					                .enumerate()
 | 
				
			||||||
 | 
					                .map(|(idx, far_bound)| {
 | 
				
			||||||
 | 
					                    calculate_cascade(
 | 
				
			||||||
 | 
					                        aspect_ratio,
 | 
				
			||||||
 | 
					                        tan_half_fov,
 | 
				
			||||||
 | 
					                        directional_light_shadow_map.size as f32,
 | 
				
			||||||
 | 
					                        light_to_world,
 | 
				
			||||||
 | 
					                        camera_to_light_view,
 | 
				
			||||||
 | 
					                        // Negate bounds as -z is camera forward direction.
 | 
				
			||||||
 | 
					                        if idx > 0 {
 | 
				
			||||||
 | 
					                            (1.0 - cascades_config.overlap_proportion)
 | 
				
			||||||
 | 
					                                * -cascades_config.bounds[idx - 1]
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            0.0
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        -far_bound,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .collect();
 | 
				
			||||||
 | 
					            cascades.cascades.insert(view_entity, view_cascades);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn calculate_cascade(
 | 
				
			||||||
 | 
					    aspect_ratio: f32,
 | 
				
			||||||
 | 
					    tan_half_fov: f32,
 | 
				
			||||||
 | 
					    cascade_texture_size: f32,
 | 
				
			||||||
 | 
					    light_to_world: Mat4,
 | 
				
			||||||
 | 
					    camera_to_light: Mat4,
 | 
				
			||||||
 | 
					    z_near: f32,
 | 
				
			||||||
 | 
					    z_far: f32,
 | 
				
			||||||
 | 
					) -> Cascade {
 | 
				
			||||||
 | 
					    debug_assert!(z_near <= 0.0, "z_near {} must be <= 0.0", z_near);
 | 
				
			||||||
 | 
					    debug_assert!(z_far <= 0.0, "z_far {} must be <= 0.0", z_far);
 | 
				
			||||||
 | 
					    // NOTE: This whole function is very sensitive to floating point precision and instability and
 | 
				
			||||||
 | 
					    // has followed instructions to avoid view dependence from the section on cascade shadow maps in
 | 
				
			||||||
 | 
					    // Eric Lengyel's Foundations of Game Engine Development 2: Rendering. Be very careful when
 | 
				
			||||||
 | 
					    // modifying this code!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let a = z_near.abs() * tan_half_fov;
 | 
				
			||||||
 | 
					    let b = z_far.abs() * tan_half_fov;
 | 
				
			||||||
 | 
					    // NOTE: These vertices are in a specific order: bottom right, top right, top left, bottom left
 | 
				
			||||||
 | 
					    //                                               for near then for far
 | 
				
			||||||
 | 
					    let frustum_corners = [
 | 
				
			||||||
 | 
					        Vec3A::new(a * aspect_ratio, -a, z_near),
 | 
				
			||||||
 | 
					        Vec3A::new(a * aspect_ratio, a, z_near),
 | 
				
			||||||
 | 
					        Vec3A::new(-a * aspect_ratio, a, z_near),
 | 
				
			||||||
 | 
					        Vec3A::new(-a * aspect_ratio, -a, z_near),
 | 
				
			||||||
 | 
					        Vec3A::new(b * aspect_ratio, -b, z_far),
 | 
				
			||||||
 | 
					        Vec3A::new(b * aspect_ratio, b, z_far),
 | 
				
			||||||
 | 
					        Vec3A::new(-b * aspect_ratio, b, z_far),
 | 
				
			||||||
 | 
					        Vec3A::new(-b * aspect_ratio, -b, z_far),
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut min = Vec3A::splat(f32::MAX);
 | 
				
			||||||
 | 
					    let mut max = Vec3A::splat(f32::MIN);
 | 
				
			||||||
 | 
					    for corner_camera_view in frustum_corners {
 | 
				
			||||||
 | 
					        let corner_light_view = camera_to_light.transform_point3a(corner_camera_view);
 | 
				
			||||||
 | 
					        min = min.min(corner_light_view);
 | 
				
			||||||
 | 
					        max = max.max(corner_light_view);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // NOTE: Use the larger of the frustum slice far plane diagonal and body diagonal lengths as this
 | 
				
			||||||
 | 
					    //       will be the maximum possible projection size. Use the ceiling to get an integer which is
 | 
				
			||||||
 | 
					    //       very important for floating point stability later. It is also important that these are
 | 
				
			||||||
 | 
					    //       calculated using the original camera space corner positions for floating point precision
 | 
				
			||||||
 | 
					    //       as even though the lengths using corner_light_view above should be the same, precision can
 | 
				
			||||||
 | 
					    //       introduce small but significant differences.
 | 
				
			||||||
 | 
					    // NOTE: The size remains the same unless the view frustum or cascade configuration is modified.
 | 
				
			||||||
 | 
					    let cascade_diameter = (frustum_corners[0] - frustum_corners[6])
 | 
				
			||||||
 | 
					        .length()
 | 
				
			||||||
 | 
					        .max((frustum_corners[4] - frustum_corners[6]).length())
 | 
				
			||||||
 | 
					        .ceil();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // NOTE: If we ensure that cascade_texture_size is a power of 2, then as we made cascade_diameter an
 | 
				
			||||||
 | 
					    //       integer, cascade_texel_size is then an integer multiple of a power of 2 and can be
 | 
				
			||||||
 | 
					    //       exactly represented in a floating point value.
 | 
				
			||||||
 | 
					    let cascade_texel_size = cascade_diameter / cascade_texture_size;
 | 
				
			||||||
 | 
					    // NOTE: For shadow stability it is very important that the near_plane_center is at integer
 | 
				
			||||||
 | 
					    //       multiples of the texel size to be exactly representable in a floating point value.
 | 
				
			||||||
 | 
					    let near_plane_center = Vec3A::new(
 | 
				
			||||||
 | 
					        (0.5 * (min.x + max.x) / cascade_texel_size).floor() * cascade_texel_size,
 | 
				
			||||||
 | 
					        (0.5 * (min.y + max.y) / cascade_texel_size).floor() * cascade_texel_size,
 | 
				
			||||||
 | 
					        // NOTE: max.z is the near plane for right-handed y-up
 | 
				
			||||||
 | 
					        max.z,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // It is critical for `world_to_cascade` to be stable. So rather than forming `cascade_to_world`
 | 
				
			||||||
 | 
					    // and inverting it, which risks instability due to numerical precision, we directly form
 | 
				
			||||||
 | 
					    // `world_to_cascde` as the reference material suggests.
 | 
				
			||||||
 | 
					    let light_to_world_transpose = light_to_world.transpose();
 | 
				
			||||||
 | 
					    let world_to_cascade = Mat4::from_cols(
 | 
				
			||||||
 | 
					        light_to_world_transpose.x_axis,
 | 
				
			||||||
 | 
					        light_to_world_transpose.y_axis,
 | 
				
			||||||
 | 
					        light_to_world_transpose.z_axis,
 | 
				
			||||||
 | 
					        (-near_plane_center).extend(1.0),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Right-handed orthographic projection, centered at `near_plane_center`.
 | 
				
			||||||
 | 
					    // NOTE: This is different from the reference material, as we use reverse Z.
 | 
				
			||||||
 | 
					    let r = (max.z - min.z).recip();
 | 
				
			||||||
 | 
					    let cascade_projection = Mat4::from_cols(
 | 
				
			||||||
 | 
					        Vec4::new(2.0 / cascade_diameter, 0.0, 0.0, 0.0),
 | 
				
			||||||
 | 
					        Vec4::new(0.0, 2.0 / cascade_diameter, 0.0, 0.0),
 | 
				
			||||||
 | 
					        Vec4::new(0.0, 0.0, r, 0.0),
 | 
				
			||||||
 | 
					        Vec4::new(0.0, 0.0, 1.0, 1.0),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let cascade_view_projection = cascade_projection * world_to_cascade;
 | 
				
			||||||
 | 
					    Cascade {
 | 
				
			||||||
 | 
					        view_transform: world_to_cascade.inverse(),
 | 
				
			||||||
 | 
					        projection: cascade_projection,
 | 
				
			||||||
 | 
					        view_projection: cascade_view_projection,
 | 
				
			||||||
 | 
					        texel_size: cascade_texel_size,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -293,6 +513,7 @@ pub struct NotShadowReceiver;
 | 
				
			|||||||
pub enum SimulationLightSystems {
 | 
					pub enum SimulationLightSystems {
 | 
				
			||||||
    AddClusters,
 | 
					    AddClusters,
 | 
				
			||||||
    AssignLightsToClusters,
 | 
					    AssignLightsToClusters,
 | 
				
			||||||
 | 
					    UpdateDirectionalLightCascades,
 | 
				
			||||||
    UpdateLightFrusta,
 | 
					    UpdateLightFrusta,
 | 
				
			||||||
    CheckLightVisibility,
 | 
					    CheckLightVisibility,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1462,19 +1683,18 @@ fn project_to_plane_y(y_light: Sphere, y_plane: Plane, is_orthographic: bool) ->
 | 
				
			|||||||
pub fn update_directional_light_frusta(
 | 
					pub fn update_directional_light_frusta(
 | 
				
			||||||
    mut views: Query<
 | 
					    mut views: Query<
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            &GlobalTransform,
 | 
					            &Cascades,
 | 
				
			||||||
            &DirectionalLight,
 | 
					            &DirectionalLight,
 | 
				
			||||||
            &mut Frustum,
 | 
					 | 
				
			||||||
            &ComputedVisibility,
 | 
					            &ComputedVisibility,
 | 
				
			||||||
 | 
					            &mut CascadesFrusta,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            Or<(Changed<GlobalTransform>, Changed<DirectionalLight>)>,
 | 
					 | 
				
			||||||
            // Prevents this query from conflicting with camera queries.
 | 
					            // Prevents this query from conflicting with camera queries.
 | 
				
			||||||
            Without<Camera>,
 | 
					            Without<Camera>,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
    >,
 | 
					    >,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    for (transform, directional_light, mut frustum, visibility) in &mut views {
 | 
					    for (cascades, directional_light, visibility, mut frusta) in &mut views {
 | 
				
			||||||
        // The frustum is used for culling meshes to the light for shadow mapping
 | 
					        // The frustum is used for culling meshes to the light for shadow mapping
 | 
				
			||||||
        // so if shadow mapping is disabled for this light, then the frustum is
 | 
					        // so if shadow mapping is disabled for this light, then the frustum is
 | 
				
			||||||
        // not needed.
 | 
					        // not needed.
 | 
				
			||||||
@ -1482,14 +1702,19 @@ pub fn update_directional_light_frusta(
 | 
				
			|||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let view_projection = directional_light.shadow_projection.get_projection_matrix()
 | 
					        frusta.frusta = cascades
 | 
				
			||||||
            * transform.compute_matrix().inverse();
 | 
					            .cascades
 | 
				
			||||||
        *frustum = Frustum::from_view_projection(
 | 
					            .iter()
 | 
				
			||||||
            &view_projection,
 | 
					            .map(|(view, cascades)| {
 | 
				
			||||||
            &transform.translation(),
 | 
					                (
 | 
				
			||||||
            &transform.back(),
 | 
					                    *view,
 | 
				
			||||||
            directional_light.shadow_projection.far(),
 | 
					                    cascades
 | 
				
			||||||
        );
 | 
					                        .iter()
 | 
				
			||||||
 | 
					                        .map(|c| Frustum::from_view_projection(&c.view_projection))
 | 
				
			||||||
 | 
					                        .collect::<Vec<_>>(),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1528,7 +1753,7 @@ pub fn update_point_light_frusta(
 | 
				
			|||||||
            let view = view_translation * *view_rotation;
 | 
					            let view = view_translation * *view_rotation;
 | 
				
			||||||
            let view_projection = projection * view.compute_matrix().inverse();
 | 
					            let view_projection = projection * view.compute_matrix().inverse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            *frustum = Frustum::from_view_projection(
 | 
					            *frustum = Frustum::from_view_projection_custom_far(
 | 
				
			||||||
                &view_projection,
 | 
					                &view_projection,
 | 
				
			||||||
                &transform.translation(),
 | 
					                &transform.translation(),
 | 
				
			||||||
                &view_backward,
 | 
					                &view_backward,
 | 
				
			||||||
@ -1563,7 +1788,7 @@ pub fn update_spot_light_frusta(
 | 
				
			|||||||
        let spot_projection = spot_light_projection_matrix(spot_light.outer_angle);
 | 
					        let spot_projection = spot_light_projection_matrix(spot_light.outer_angle);
 | 
				
			||||||
        let view_projection = spot_projection * spot_view.inverse();
 | 
					        let view_projection = spot_projection * spot_view.inverse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        *frustum = Frustum::from_view_projection(
 | 
					        *frustum = Frustum::from_view_projection_custom_far(
 | 
				
			||||||
            &view_projection,
 | 
					            &view_projection,
 | 
				
			||||||
            &transform.translation(),
 | 
					            &transform.translation(),
 | 
				
			||||||
            &view_backward,
 | 
					            &view_backward,
 | 
				
			||||||
@ -1591,10 +1816,10 @@ pub fn check_light_mesh_visibility(
 | 
				
			|||||||
    mut directional_lights: Query<
 | 
					    mut directional_lights: Query<
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            &DirectionalLight,
 | 
					            &DirectionalLight,
 | 
				
			||||||
            &Frustum,
 | 
					            &CascadesFrusta,
 | 
				
			||||||
            &mut VisibleEntities,
 | 
					            &mut CascadesVisibleEntities,
 | 
				
			||||||
            Option<&RenderLayers>,
 | 
					            Option<&RenderLayers>,
 | 
				
			||||||
            &ComputedVisibility,
 | 
					            &mut ComputedVisibility,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        Without<SpotLight>,
 | 
					        Without<SpotLight>,
 | 
				
			||||||
    >,
 | 
					    >,
 | 
				
			||||||
@ -1628,13 +1853,34 @@ pub fn check_light_mesh_visibility(
 | 
				
			|||||||
    // Directional lights
 | 
					    // Directional lights
 | 
				
			||||||
    for (
 | 
					    for (
 | 
				
			||||||
        directional_light,
 | 
					        directional_light,
 | 
				
			||||||
        frustum,
 | 
					        frusta,
 | 
				
			||||||
        mut visible_entities,
 | 
					        mut visible_entities,
 | 
				
			||||||
        maybe_view_mask,
 | 
					        maybe_view_mask,
 | 
				
			||||||
        light_computed_visibility,
 | 
					        light_computed_visibility,
 | 
				
			||||||
    ) in &mut directional_lights
 | 
					    ) in &mut directional_lights
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        visible_entities.entities.clear();
 | 
					        // Re-use already allocated entries where possible.
 | 
				
			||||||
 | 
					        let mut views_to_remove = Vec::new();
 | 
				
			||||||
 | 
					        for (view, cascade_view_entities) in visible_entities.entities.iter_mut() {
 | 
				
			||||||
 | 
					            match frusta.frusta.get(view) {
 | 
				
			||||||
 | 
					                Some(view_frusta) => {
 | 
				
			||||||
 | 
					                    cascade_view_entities.resize(view_frusta.len(), Default::default());
 | 
				
			||||||
 | 
					                    cascade_view_entities
 | 
				
			||||||
 | 
					                        .iter_mut()
 | 
				
			||||||
 | 
					                        .for_each(|x| x.entities.clear());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                None => views_to_remove.push(*view),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (view, frusta) in frusta.frusta.iter() {
 | 
				
			||||||
 | 
					            visible_entities
 | 
				
			||||||
 | 
					                .entities
 | 
				
			||||||
 | 
					                .entry(*view)
 | 
				
			||||||
 | 
					                .or_insert_with(|| vec![VisibleEntities::default(); frusta.len()]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for v in views_to_remove {
 | 
				
			||||||
 | 
					            visible_entities.entities.remove(&v);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // NOTE: If shadow mapping is disabled for the light then it must have no visible entities
 | 
					        // NOTE: If shadow mapping is disabled for the light then it must have no visible entities
 | 
				
			||||||
        if !directional_light.shadows_enabled || !light_computed_visibility.is_visible() {
 | 
					        if !directional_light.shadows_enabled || !light_computed_visibility.is_visible() {
 | 
				
			||||||
@ -1657,16 +1903,30 @@ pub fn check_light_mesh_visibility(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // If we have an aabb and transform, do frustum culling
 | 
					            // If we have an aabb and transform, do frustum culling
 | 
				
			||||||
            if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
 | 
					            if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
 | 
				
			||||||
                if !frustum.intersects_obb(aabb, &transform.compute_matrix(), true) {
 | 
					                for (view, view_frusta) in frusta.frusta.iter() {
 | 
				
			||||||
                    continue;
 | 
					                    let view_visible_entities = visible_entities
 | 
				
			||||||
 | 
					                        .entities
 | 
				
			||||||
 | 
					                        .get_mut(view)
 | 
				
			||||||
 | 
					                        .expect("Per-view visible entities should have been inserted already");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    for (frustum, frustum_visible_entities) in
 | 
				
			||||||
 | 
					                        view_frusta.iter().zip(view_visible_entities)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        // Disable near-plane culling, as a shadow caster could lie before the near plane.
 | 
				
			||||||
 | 
					                        if !frustum.intersects_obb(aabb, &transform.compute_matrix(), false, true) {
 | 
				
			||||||
 | 
					                            continue;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        computed_visibility.set_visible_in_view();
 | 
				
			||||||
 | 
					                        frustum_visible_entities.entities.push(entity);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
            computed_visibility.set_visible_in_view();
 | 
					 | 
				
			||||||
            visible_entities.entities.push(entity);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        shrink_entities(&mut visible_entities);
 | 
					        for (_, cascade_view_entities) in visible_entities.entities.iter_mut() {
 | 
				
			||||||
 | 
					            cascade_view_entities.iter_mut().for_each(shrink_entities);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for visible_lights in &visible_point_lights {
 | 
					    for visible_lights in &visible_point_lights {
 | 
				
			||||||
@ -1724,7 +1984,7 @@ pub fn check_light_mesh_visibility(
 | 
				
			|||||||
                            .iter()
 | 
					                            .iter()
 | 
				
			||||||
                            .zip(cubemap_visible_entities.iter_mut())
 | 
					                            .zip(cubemap_visible_entities.iter_mut())
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            if frustum.intersects_obb(aabb, &model_to_world, true) {
 | 
					                            if frustum.intersects_obb(aabb, &model_to_world, true, true) {
 | 
				
			||||||
                                computed_visibility.set_visible_in_view();
 | 
					                                computed_visibility.set_visible_in_view();
 | 
				
			||||||
                                visible_entities.entities.push(entity);
 | 
					                                visible_entities.entities.push(entity);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
@ -1784,7 +2044,7 @@ pub fn check_light_mesh_visibility(
 | 
				
			|||||||
                            continue;
 | 
					                            continue;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if frustum.intersects_obb(aabb, &model_to_world, true) {
 | 
					                        if frustum.intersects_obb(aabb, &model_to_world, true, true) {
 | 
				
			||||||
                            computed_visibility.set_visible_in_view();
 | 
					                            computed_visibility.set_visible_in_view();
 | 
				
			||||||
                            visible_entities.entities.push(entity);
 | 
					                            visible_entities.entities.push(entity);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
				
			|||||||
@ -38,5 +38,9 @@ fn vertex(vertex: Vertex) -> VertexOutput {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    var out: VertexOutput;
 | 
					    var out: VertexOutput;
 | 
				
			||||||
    out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
 | 
					    out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
 | 
				
			||||||
 | 
					#ifdef DEPTH_CLAMP_ORTHO
 | 
				
			||||||
 | 
					        out.clip_position.z = min(out.clip_position.z, 1.0);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return out;
 | 
					    return out;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,9 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    directional_light_order, point_light_order, AmbientLight, Clusters, CubemapVisibleEntities,
 | 
					    directional_light_order, point_light_order, AmbientLight, Cascade, CascadeShadowConfig,
 | 
				
			||||||
    DirectionalLight, DirectionalLightShadowMap, DrawMesh, GlobalVisiblePointLights, MeshPipeline,
 | 
					    Cascades, CascadesVisibleEntities, Clusters, CubemapVisibleEntities, DirectionalLight,
 | 
				
			||||||
    NotShadowCaster, PointLight, PointLightShadowMap, SetMeshBindGroup, SpotLight,
 | 
					    DirectionalLightShadowMap, DrawMesh, GlobalVisiblePointLights, MeshPipeline, NotShadowCaster,
 | 
				
			||||||
    VisiblePointLights, SHADOW_SHADER_HANDLE,
 | 
					    PointLight, PointLightShadowMap, SetMeshBindGroup, SpotLight, VisiblePointLights,
 | 
				
			||||||
 | 
					    SHADOW_SHADER_HANDLE,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use bevy_asset::Handle;
 | 
					use bevy_asset::Handle;
 | 
				
			||||||
use bevy_core_pipeline::core_3d::Transparent3d;
 | 
					use bevy_core_pipeline::core_3d::Transparent3d;
 | 
				
			||||||
@ -10,9 +11,9 @@ use bevy_ecs::{
 | 
				
			|||||||
    prelude::*,
 | 
					    prelude::*,
 | 
				
			||||||
    system::{lifetimeless::*, SystemParamItem},
 | 
					    system::{lifetimeless::*, SystemParamItem},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
 | 
					use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
 | 
				
			||||||
use bevy_render::{
 | 
					use bevy_render::{
 | 
				
			||||||
    camera::{Camera, CameraProjection},
 | 
					    camera::Camera,
 | 
				
			||||||
    color::Color,
 | 
					    color::Color,
 | 
				
			||||||
    mesh::{Mesh, MeshVertexBufferLayout},
 | 
					    mesh::{Mesh, MeshVertexBufferLayout},
 | 
				
			||||||
    render_asset::RenderAssets,
 | 
					    render_asset::RenderAssets,
 | 
				
			||||||
@ -61,15 +62,16 @@ pub struct ExtractedPointLight {
 | 
				
			|||||||
    spot_light_angles: Option<(f32, f32)>,
 | 
					    spot_light_angles: Option<(f32, f32)>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Component)]
 | 
					#[derive(Component, Debug)]
 | 
				
			||||||
pub struct ExtractedDirectionalLight {
 | 
					pub struct ExtractedDirectionalLight {
 | 
				
			||||||
    color: Color,
 | 
					    color: Color,
 | 
				
			||||||
    illuminance: f32,
 | 
					    illuminance: f32,
 | 
				
			||||||
    transform: GlobalTransform,
 | 
					    transform: GlobalTransform,
 | 
				
			||||||
    projection: Mat4,
 | 
					 | 
				
			||||||
    shadows_enabled: bool,
 | 
					    shadows_enabled: bool,
 | 
				
			||||||
    shadow_depth_bias: f32,
 | 
					    shadow_depth_bias: f32,
 | 
				
			||||||
    shadow_normal_bias: f32,
 | 
					    shadow_normal_bias: f32,
 | 
				
			||||||
 | 
					    cascade_shadow_config: CascadeShadowConfig,
 | 
				
			||||||
 | 
					    cascades: HashMap<Entity, Vec<Cascade>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Copy, Clone, ShaderType, Default, Debug)]
 | 
					#[derive(Copy, Clone, ShaderType, Default, Debug)]
 | 
				
			||||||
@ -174,13 +176,23 @@ bitflags::bitflags! {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Copy, Clone, ShaderType, Default, Debug)]
 | 
					#[derive(Copy, Clone, ShaderType, Default, Debug)]
 | 
				
			||||||
pub struct GpuDirectionalLight {
 | 
					pub struct GpuDirectionalCascade {
 | 
				
			||||||
    view_projection: Mat4,
 | 
					    view_projection: Mat4,
 | 
				
			||||||
 | 
					    texel_size: f32,
 | 
				
			||||||
 | 
					    far_bound: f32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Copy, Clone, ShaderType, Default, Debug)]
 | 
				
			||||||
 | 
					pub struct GpuDirectionalLight {
 | 
				
			||||||
 | 
					    cascades: [GpuDirectionalCascade; MAX_CASCADES_PER_LIGHT],
 | 
				
			||||||
    color: Vec4,
 | 
					    color: Vec4,
 | 
				
			||||||
    dir_to_light: Vec3,
 | 
					    dir_to_light: Vec3,
 | 
				
			||||||
    flags: u32,
 | 
					    flags: u32,
 | 
				
			||||||
    shadow_depth_bias: f32,
 | 
					    shadow_depth_bias: f32,
 | 
				
			||||||
    shadow_normal_bias: f32,
 | 
					    shadow_normal_bias: f32,
 | 
				
			||||||
 | 
					    num_cascades: u32,
 | 
				
			||||||
 | 
					    cascades_overlap_proportion: f32,
 | 
				
			||||||
 | 
					    depth_texture_base_index: u32,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
 | 
					// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
 | 
				
			||||||
@ -211,6 +223,10 @@ pub struct GpuLights {
 | 
				
			|||||||
// NOTE: this must be kept in sync with the same constants in pbr.frag
 | 
					// NOTE: this must be kept in sync with the same constants in pbr.frag
 | 
				
			||||||
pub const MAX_UNIFORM_BUFFER_POINT_LIGHTS: usize = 256;
 | 
					pub const MAX_UNIFORM_BUFFER_POINT_LIGHTS: usize = 256;
 | 
				
			||||||
pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
 | 
					pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
 | 
				
			||||||
 | 
					#[cfg(not(feature = "webgl"))]
 | 
				
			||||||
 | 
					pub const MAX_CASCADES_PER_LIGHT: usize = 4;
 | 
				
			||||||
 | 
					#[cfg(feature = "webgl")]
 | 
				
			||||||
 | 
					pub const MAX_CASCADES_PER_LIGHT: usize = 1;
 | 
				
			||||||
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
 | 
					pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Resource, Clone)]
 | 
					#[derive(Resource, Clone)]
 | 
				
			||||||
@ -279,6 +295,7 @@ bitflags::bitflags! {
 | 
				
			|||||||
    #[repr(transparent)]
 | 
					    #[repr(transparent)]
 | 
				
			||||||
    pub struct ShadowPipelineKey: u32 {
 | 
					    pub struct ShadowPipelineKey: u32 {
 | 
				
			||||||
        const NONE               = 0;
 | 
					        const NONE               = 0;
 | 
				
			||||||
 | 
					        const DEPTH_CLAMP_ORTHO  = 1;
 | 
				
			||||||
        const PRIMITIVE_TOPOLOGY_RESERVED_BITS = ShadowPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << ShadowPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
 | 
					        const PRIMITIVE_TOPOLOGY_RESERVED_BITS = ShadowPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << ShadowPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -324,6 +341,15 @@ impl SpecializedMeshPipeline for ShadowPipeline {
 | 
				
			|||||||
            "MAX_DIRECTIONAL_LIGHTS".to_string(),
 | 
					            "MAX_DIRECTIONAL_LIGHTS".to_string(),
 | 
				
			||||||
            MAX_DIRECTIONAL_LIGHTS as u32,
 | 
					            MAX_DIRECTIONAL_LIGHTS as u32,
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
 | 
					        shader_defs.push(ShaderDefVal::UInt(
 | 
				
			||||||
 | 
					            "MAX_CASCADES_PER_LIGHT".to_string(),
 | 
				
			||||||
 | 
					            MAX_CASCADES_PER_LIGHT as u32,
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if key.contains(ShadowPipelineKey::DEPTH_CLAMP_ORTHO) {
 | 
				
			||||||
 | 
					            // Avoid clipping shadow casters that are behind the near plane.
 | 
				
			||||||
 | 
					            shader_defs.push("DEPTH_CLAMP_ORTHO".into());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
 | 
					        if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
 | 
				
			||||||
            && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
 | 
					            && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
 | 
				
			||||||
@ -437,7 +463,9 @@ pub fn extract_lights(
 | 
				
			|||||||
            (
 | 
					            (
 | 
				
			||||||
                Entity,
 | 
					                Entity,
 | 
				
			||||||
                &DirectionalLight,
 | 
					                &DirectionalLight,
 | 
				
			||||||
                &VisibleEntities,
 | 
					                &CascadesVisibleEntities,
 | 
				
			||||||
 | 
					                &Cascades,
 | 
				
			||||||
 | 
					                &CascadeShadowConfig,
 | 
				
			||||||
                &GlobalTransform,
 | 
					                &GlobalTransform,
 | 
				
			||||||
                &ComputedVisibility,
 | 
					                &ComputedVisibility,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@ -546,22 +574,20 @@ pub fn extract_lights(
 | 
				
			|||||||
    *previous_spot_lights_len = spot_lights_values.len();
 | 
					    *previous_spot_lights_len = spot_lights_values.len();
 | 
				
			||||||
    commands.insert_or_spawn_batch(spot_lights_values);
 | 
					    commands.insert_or_spawn_batch(spot_lights_values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (entity, directional_light, visible_entities, transform, visibility) in
 | 
					    for (
 | 
				
			||||||
        directional_lights.iter()
 | 
					        entity,
 | 
				
			||||||
 | 
					        directional_light,
 | 
				
			||||||
 | 
					        visible_entities,
 | 
				
			||||||
 | 
					        cascades,
 | 
				
			||||||
 | 
					        cascade_config,
 | 
				
			||||||
 | 
					        transform,
 | 
				
			||||||
 | 
					        visibility,
 | 
				
			||||||
 | 
					    ) in directional_lights.iter()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if !visibility.is_visible() {
 | 
					        if !visibility.is_visible() {
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 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/
 | 
					 | 
				
			||||||
        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
 | 
					        // 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((
 | 
				
			||||||
@ -569,11 +595,12 @@ pub fn extract_lights(
 | 
				
			|||||||
                color: directional_light.color,
 | 
					                color: directional_light.color,
 | 
				
			||||||
                illuminance: directional_light.illuminance,
 | 
					                illuminance: directional_light.illuminance,
 | 
				
			||||||
                transform: *transform,
 | 
					                transform: *transform,
 | 
				
			||||||
                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,
 | 
				
			||||||
                shadow_normal_bias: directional_light.shadow_normal_bias
 | 
					                // The factor of SQRT_2 is for the worst-case diagonal offset
 | 
				
			||||||
                    * directional_light_texel_size,
 | 
					                shadow_normal_bias: directional_light.shadow_normal_bias * std::f32::consts::SQRT_2,
 | 
				
			||||||
 | 
					                cascade_shadow_config: cascade_config.clone(),
 | 
				
			||||||
 | 
					                cascades: cascades.cascades.clone(),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            render_visible_entities,
 | 
					            render_visible_entities,
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
@ -696,6 +723,7 @@ pub struct LightMeta {
 | 
				
			|||||||
pub enum LightEntity {
 | 
					pub enum LightEntity {
 | 
				
			||||||
    Directional {
 | 
					    Directional {
 | 
				
			||||||
        light_entity: Entity,
 | 
					        light_entity: Entity,
 | 
				
			||||||
 | 
					        cascade_index: usize,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    Point {
 | 
					    Point {
 | 
				
			||||||
        light_entity: Entity,
 | 
					        light_entity: Entity,
 | 
				
			||||||
@ -770,6 +798,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
    point_light_shadow_map: Res<PointLightShadowMap>,
 | 
					    point_light_shadow_map: Res<PointLightShadowMap>,
 | 
				
			||||||
    directional_light_shadow_map: Res<DirectionalLightShadowMap>,
 | 
					    directional_light_shadow_map: Res<DirectionalLightShadowMap>,
 | 
				
			||||||
    mut max_directional_lights_warning_emitted: Local<bool>,
 | 
					    mut max_directional_lights_warning_emitted: Local<bool>,
 | 
				
			||||||
 | 
					    mut max_cascades_per_light_warning_emitted: Local<bool>,
 | 
				
			||||||
    point_lights: Query<(Entity, &ExtractedPointLight)>,
 | 
					    point_lights: Query<(Entity, &ExtractedPointLight)>,
 | 
				
			||||||
    directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
 | 
					    directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
@ -807,6 +836,18 @@ pub fn prepare_lights(
 | 
				
			|||||||
        *max_directional_lights_warning_emitted = true;
 | 
					        *max_directional_lights_warning_emitted = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !*max_cascades_per_light_warning_emitted
 | 
				
			||||||
 | 
					        && directional_lights
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .any(|(_, light)| light.cascade_shadow_config.bounds.len() > MAX_CASCADES_PER_LIGHT)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					            "The number of cascades configured for a directional light exceeds the supported limit of {}.",
 | 
				
			||||||
 | 
					            MAX_CASCADES_PER_LIGHT
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        *max_cascades_per_light_warning_emitted = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let point_light_count = point_lights
 | 
					    let point_light_count = point_lights
 | 
				
			||||||
        .iter()
 | 
					        .iter()
 | 
				
			||||||
        .filter(|light| light.1.spot_light_angles.is_none())
 | 
					        .filter(|light| light.1.spot_light_angles.is_none())
 | 
				
			||||||
@ -818,18 +859,18 @@ pub fn prepare_lights(
 | 
				
			|||||||
        .count()
 | 
					        .count()
 | 
				
			||||||
        .min(max_texture_cubes);
 | 
					        .min(max_texture_cubes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let directional_shadow_maps_count = directional_lights
 | 
					    let directional_shadow_enabled_count = directional_lights
 | 
				
			||||||
        .iter()
 | 
					        .iter()
 | 
				
			||||||
        .take(MAX_DIRECTIONAL_LIGHTS)
 | 
					        .take(MAX_DIRECTIONAL_LIGHTS)
 | 
				
			||||||
        .filter(|(_, light)| light.shadows_enabled)
 | 
					        .filter(|(_, light)| light.shadows_enabled)
 | 
				
			||||||
        .count()
 | 
					        .count()
 | 
				
			||||||
        .min(max_texture_array_layers);
 | 
					        .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let spot_light_shadow_maps_count = point_lights
 | 
					    let spot_light_shadow_maps_count = point_lights
 | 
				
			||||||
        .iter()
 | 
					        .iter()
 | 
				
			||||||
        .filter(|(_, light)| light.shadows_enabled && light.spot_light_angles.is_some())
 | 
					        .filter(|(_, light)| light.shadows_enabled && light.spot_light_angles.is_some())
 | 
				
			||||||
        .count()
 | 
					        .count()
 | 
				
			||||||
        .min(max_texture_array_layers - directional_shadow_maps_count);
 | 
					        .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Sort lights by
 | 
					    // Sort lights by
 | 
				
			||||||
    // - point-light vs spot-light, so that we can iterate point lights and spot lights in contiguous blocks in the fragment shader,
 | 
					    // - point-light vs spot-light, so that we can iterate point lights and spot lights in contiguous blocks in the fragment shader,
 | 
				
			||||||
@ -931,7 +972,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
 | 
					    let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
 | 
				
			||||||
 | 
					    let mut num_directional_cascades_enabled = 0usize;
 | 
				
			||||||
    for (index, (_light_entity, light)) in directional_lights
 | 
					    for (index, (_light_entity, light)) in directional_lights
 | 
				
			||||||
        .iter()
 | 
					        .iter()
 | 
				
			||||||
        .enumerate()
 | 
					        .enumerate()
 | 
				
			||||||
@ -940,13 +981,10 @@ pub fn prepare_lights(
 | 
				
			|||||||
        let mut flags = DirectionalLightFlags::NONE;
 | 
					        let mut flags = DirectionalLightFlags::NONE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Lights are sorted, shadow enabled lights are first
 | 
					        // Lights are sorted, shadow enabled lights are first
 | 
				
			||||||
        if light.shadows_enabled && (index < directional_shadow_maps_count) {
 | 
					        if light.shadows_enabled && (index < directional_shadow_enabled_count) {
 | 
				
			||||||
            flags |= DirectionalLightFlags::SHADOWS_ENABLED;
 | 
					            flags |= DirectionalLightFlags::SHADOWS_ENABLED;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // direction is negated to be ready for N.L
 | 
					 | 
				
			||||||
        let dir_to_light = light.transform.back();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // convert from illuminance (lux) to candelas
 | 
					        // convert from illuminance (lux) to candelas
 | 
				
			||||||
        //
 | 
					        //
 | 
				
			||||||
        // exposure is hard coded at the moment but should be replaced
 | 
					        // exposure is hard coded at the moment but should be replaced
 | 
				
			||||||
@ -959,22 +997,29 @@ 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: For the purpose of rendering shadow maps, we apply the directional light's transform to an orthographic camera
 | 
					        let num_cascades = light
 | 
				
			||||||
        let view = light.transform.compute_matrix().inverse();
 | 
					            .cascade_shadow_config
 | 
				
			||||||
        // NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast
 | 
					            .bounds
 | 
				
			||||||
        let projection = light.projection;
 | 
					            .len()
 | 
				
			||||||
 | 
					            .min(MAX_CASCADES_PER_LIGHT);
 | 
				
			||||||
        gpu_directional_lights[index] = GpuDirectionalLight {
 | 
					        gpu_directional_lights[index] = GpuDirectionalLight {
 | 
				
			||||||
 | 
					            // Filled in later.
 | 
				
			||||||
 | 
					            cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
 | 
				
			||||||
            // premultiply color by intensity
 | 
					            // premultiply color by intensity
 | 
				
			||||||
            // we don't use the alpha at all, so no reason to multiply only [0..3]
 | 
					            // we don't use the alpha at all, so no reason to multiply only [0..3]
 | 
				
			||||||
            color: Vec4::from_slice(&light.color.as_linear_rgba_f32()) * intensity,
 | 
					            color: Vec4::from_slice(&light.color.as_linear_rgba_f32()) * intensity,
 | 
				
			||||||
            dir_to_light,
 | 
					            // direction is negated to be ready for N.L
 | 
				
			||||||
            // NOTE: * view is correct, it should not be view.inverse() here
 | 
					            dir_to_light: light.transform.back(),
 | 
				
			||||||
            view_projection: projection * view,
 | 
					 | 
				
			||||||
            flags: flags.bits,
 | 
					            flags: flags.bits,
 | 
				
			||||||
            shadow_depth_bias: light.shadow_depth_bias,
 | 
					            shadow_depth_bias: light.shadow_depth_bias,
 | 
				
			||||||
            shadow_normal_bias: light.shadow_normal_bias,
 | 
					            shadow_normal_bias: light.shadow_normal_bias,
 | 
				
			||||||
 | 
					            num_cascades: num_cascades as u32,
 | 
				
			||||||
 | 
					            cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
 | 
				
			||||||
 | 
					            depth_texture_base_index: num_directional_cascades_enabled as u32,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					        if index < directional_shadow_enabled_count {
 | 
				
			||||||
 | 
					            num_directional_cascades_enabled += num_cascades;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    global_light_meta.gpu_point_lights.set(gpu_point_lights);
 | 
					    global_light_meta.gpu_point_lights.set(gpu_point_lights);
 | 
				
			||||||
@ -1008,7 +1053,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
                        .min(render_device.limits().max_texture_dimension_2d),
 | 
					                        .min(render_device.limits().max_texture_dimension_2d),
 | 
				
			||||||
                    height: (directional_light_shadow_map.size as u32)
 | 
					                    height: (directional_light_shadow_map.size as u32)
 | 
				
			||||||
                        .min(render_device.limits().max_texture_dimension_2d),
 | 
					                        .min(render_device.limits().max_texture_dimension_2d),
 | 
				
			||||||
                    depth_or_array_layers: (directional_shadow_maps_count
 | 
					                    depth_or_array_layers: (num_directional_cascades_enabled
 | 
				
			||||||
                        + spot_light_shadow_maps_count)
 | 
					                        + spot_light_shadow_maps_count)
 | 
				
			||||||
                        .max(1) as u32,
 | 
					                        .max(1) as u32,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
@ -1031,7 +1076,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
 | 
					        let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
 | 
				
			||||||
        let gpu_lights = GpuLights {
 | 
					        let mut gpu_lights = GpuLights {
 | 
				
			||||||
            directional_lights: gpu_directional_lights,
 | 
					            directional_lights: gpu_directional_lights,
 | 
				
			||||||
            ambient_color: Vec4::from_slice(&ambient_light.color.as_linear_rgba_f32())
 | 
					            ambient_color: Vec4::from_slice(&ambient_light.color.as_linear_rgba_f32())
 | 
				
			||||||
                * ambient_light.brightness,
 | 
					                * ambient_light.brightness,
 | 
				
			||||||
@ -1043,10 +1088,10 @@ pub fn prepare_lights(
 | 
				
			|||||||
            ),
 | 
					            ),
 | 
				
			||||||
            cluster_dimensions: clusters.dimensions.extend(n_clusters),
 | 
					            cluster_dimensions: clusters.dimensions.extend(n_clusters),
 | 
				
			||||||
            n_directional_lights: directional_lights.iter().len() as u32,
 | 
					            n_directional_lights: directional_lights.iter().len() as u32,
 | 
				
			||||||
            // spotlight shadow maps are stored in the directional light array, starting at directional_shadow_maps_count.
 | 
					            // spotlight shadow maps are stored in the directional light array, starting at num_directional_cascades_enabled.
 | 
				
			||||||
            // the spot lights themselves start in the light array at point_light_count. so to go from light
 | 
					            // the spot lights themselves start in the light array at point_light_count. so to go from light
 | 
				
			||||||
            // index to shadow map index, we need to subtract point light count and add directional shadowmap count.
 | 
					            // index to shadow map index, we need to subtract point light count and add directional shadowmap count.
 | 
				
			||||||
            spot_light_shadowmap_offset: directional_shadow_maps_count as i32
 | 
					            spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
 | 
				
			||||||
                - point_light_count as i32,
 | 
					                - point_light_count as i32,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1099,6 +1144,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
                                point_light_shadow_map.size as u32,
 | 
					                                point_light_shadow_map.size as u32,
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            transform: view_translation * *view_rotation,
 | 
					                            transform: view_translation * *view_rotation,
 | 
				
			||||||
 | 
					                            view_projection: None,
 | 
				
			||||||
                            projection: cube_face_projection,
 | 
					                            projection: cube_face_projection,
 | 
				
			||||||
                            hdr: false,
 | 
					                            hdr: false,
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
@ -1137,7 +1183,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
                        aspect: TextureAspect::All,
 | 
					                        aspect: TextureAspect::All,
 | 
				
			||||||
                        base_mip_level: 0,
 | 
					                        base_mip_level: 0,
 | 
				
			||||||
                        mip_level_count: None,
 | 
					                        mip_level_count: None,
 | 
				
			||||||
                        base_array_layer: (directional_shadow_maps_count + light_index) as u32,
 | 
					                        base_array_layer: (num_directional_cascades_enabled + light_index) as u32,
 | 
				
			||||||
                        array_layer_count: NonZeroU32::new(1),
 | 
					                        array_layer_count: NonZeroU32::new(1),
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1156,6 +1202,7 @@ pub fn prepare_lights(
 | 
				
			|||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                        transform: spot_view_transform,
 | 
					                        transform: spot_view_transform,
 | 
				
			||||||
                        projection: spot_projection,
 | 
					                        projection: spot_projection,
 | 
				
			||||||
 | 
					                        view_projection: None,
 | 
				
			||||||
                        hdr: false,
 | 
					                        hdr: false,
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    RenderPhase::<Shadow>::default(),
 | 
					                    RenderPhase::<Shadow>::default(),
 | 
				
			||||||
@ -1167,47 +1214,71 @@ pub fn prepare_lights(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // directional lights
 | 
					        // directional lights
 | 
				
			||||||
 | 
					        let mut directional_depth_texture_array_index = 0u32;
 | 
				
			||||||
        for (light_index, &(light_entity, light)) in directional_lights
 | 
					        for (light_index, &(light_entity, light)) in directional_lights
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .enumerate()
 | 
					            .enumerate()
 | 
				
			||||||
            .take(directional_shadow_maps_count)
 | 
					            .take(directional_shadow_enabled_count)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            let depth_texture_view =
 | 
					            for (cascade_index, (cascade, bound)) in light
 | 
				
			||||||
                directional_light_depth_texture
 | 
					                .cascades
 | 
				
			||||||
                    .texture
 | 
					                .get(&entity)
 | 
				
			||||||
                    .create_view(&TextureViewDescriptor {
 | 
					                .unwrap()
 | 
				
			||||||
                        label: Some("directional_light_shadow_map_texture_view"),
 | 
					                .iter()
 | 
				
			||||||
                        format: None,
 | 
					                .take(MAX_CASCADES_PER_LIGHT)
 | 
				
			||||||
                        dimension: Some(TextureViewDimension::D2),
 | 
					                .zip(&light.cascade_shadow_config.bounds)
 | 
				
			||||||
                        aspect: TextureAspect::All,
 | 
					                .enumerate()
 | 
				
			||||||
                        base_mip_level: 0,
 | 
					            {
 | 
				
			||||||
                        mip_level_count: None,
 | 
					                gpu_lights.directional_lights[light_index].cascades[cascade_index] =
 | 
				
			||||||
                        base_array_layer: light_index as u32,
 | 
					                    GpuDirectionalCascade {
 | 
				
			||||||
                        array_layer_count: NonZeroU32::new(1),
 | 
					                        view_projection: cascade.view_projection,
 | 
				
			||||||
                    });
 | 
					                        texel_size: cascade.texel_size,
 | 
				
			||||||
 | 
					                        far_bound: *bound,
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let view_light_entity = commands
 | 
					                let depth_texture_view =
 | 
				
			||||||
                .spawn((
 | 
					                    directional_light_depth_texture
 | 
				
			||||||
                    ShadowView {
 | 
					                        .texture
 | 
				
			||||||
                        depth_texture_view,
 | 
					                        .create_view(&TextureViewDescriptor {
 | 
				
			||||||
                        pass_name: format!("shadow pass directional light {light_index}"),
 | 
					                            label: Some("directional_light_shadow_map_array_texture_view"),
 | 
				
			||||||
                    },
 | 
					                            format: None,
 | 
				
			||||||
                    ExtractedView {
 | 
					                            dimension: Some(TextureViewDimension::D2),
 | 
				
			||||||
                        viewport: UVec4::new(
 | 
					                            aspect: TextureAspect::All,
 | 
				
			||||||
                            0,
 | 
					                            base_mip_level: 0,
 | 
				
			||||||
                            0,
 | 
					                            mip_level_count: None,
 | 
				
			||||||
                            directional_light_shadow_map.size as u32,
 | 
					                            base_array_layer: directional_depth_texture_array_index,
 | 
				
			||||||
                            directional_light_shadow_map.size as u32,
 | 
					                            array_layer_count: NonZeroU32::new(1),
 | 
				
			||||||
                        ),
 | 
					                        });
 | 
				
			||||||
                        transform: light.transform,
 | 
					                directional_depth_texture_array_index += 1;
 | 
				
			||||||
                        projection: light.projection,
 | 
					
 | 
				
			||||||
                        hdr: false,
 | 
					                let view_light_entity = commands
 | 
				
			||||||
                    },
 | 
					                    .spawn((
 | 
				
			||||||
                    RenderPhase::<Shadow>::default(),
 | 
					                        ShadowView {
 | 
				
			||||||
                    LightEntity::Directional { light_entity },
 | 
					                            depth_texture_view,
 | 
				
			||||||
                ))
 | 
					                            pass_name: format!(
 | 
				
			||||||
                .id();
 | 
					                                "shadow pass directional light {light_index} cascade {cascade_index}"),
 | 
				
			||||||
            view_lights.push(view_light_entity);
 | 
					                        },
 | 
				
			||||||
 | 
					                        ExtractedView {
 | 
				
			||||||
 | 
					                            viewport: UVec4::new(
 | 
				
			||||||
 | 
					                                0,
 | 
				
			||||||
 | 
					                                0,
 | 
				
			||||||
 | 
					                                directional_light_shadow_map.size as u32,
 | 
				
			||||||
 | 
					                                directional_light_shadow_map.size as u32,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            transform: GlobalTransform::from(cascade.view_transform),
 | 
				
			||||||
 | 
					                            projection: cascade.projection,
 | 
				
			||||||
 | 
					                            view_projection: Some(cascade.view_projection),
 | 
				
			||||||
 | 
					                            hdr: false,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        RenderPhase::<Shadow>::default(),
 | 
				
			||||||
 | 
					                        LightEntity::Directional {
 | 
				
			||||||
 | 
					                            light_entity,
 | 
				
			||||||
 | 
					                            cascade_index,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                    .id();
 | 
				
			||||||
 | 
					                view_lights.push(view_light_entity);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let point_light_depth_texture_view =
 | 
					        let point_light_depth_texture_view =
 | 
				
			||||||
@ -1627,21 +1698,30 @@ pub fn queue_shadows(
 | 
				
			|||||||
    render_meshes: Res<RenderAssets<Mesh>>,
 | 
					    render_meshes: Res<RenderAssets<Mesh>>,
 | 
				
			||||||
    mut pipelines: ResMut<SpecializedMeshPipelines<ShadowPipeline>>,
 | 
					    mut pipelines: ResMut<SpecializedMeshPipelines<ShadowPipeline>>,
 | 
				
			||||||
    pipeline_cache: Res<PipelineCache>,
 | 
					    pipeline_cache: Res<PipelineCache>,
 | 
				
			||||||
    view_lights: Query<&ViewLightEntities>,
 | 
					    view_lights: Query<(Entity, &ViewLightEntities)>,
 | 
				
			||||||
    mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>,
 | 
					    mut view_light_shadow_phases: Query<(&LightEntity, &mut RenderPhase<Shadow>)>,
 | 
				
			||||||
    point_light_entities: Query<&CubemapVisibleEntities, With<ExtractedPointLight>>,
 | 
					    point_light_entities: Query<&CubemapVisibleEntities, With<ExtractedPointLight>>,
 | 
				
			||||||
    directional_light_entities: Query<&VisibleEntities, With<ExtractedDirectionalLight>>,
 | 
					    directional_light_entities: Query<&CascadesVisibleEntities, With<ExtractedDirectionalLight>>,
 | 
				
			||||||
    spot_light_entities: Query<&VisibleEntities, With<ExtractedPointLight>>,
 | 
					    spot_light_entities: Query<&VisibleEntities, With<ExtractedPointLight>>,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    for view_lights in &view_lights {
 | 
					    for (entity, view_lights) in &view_lights {
 | 
				
			||||||
        let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawShadowMesh>();
 | 
					        let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawShadowMesh>();
 | 
				
			||||||
        for view_light_entity in view_lights.lights.iter().copied() {
 | 
					        for view_light_entity in view_lights.lights.iter().copied() {
 | 
				
			||||||
            let (light_entity, mut shadow_phase) =
 | 
					            let (light_entity, mut shadow_phase) =
 | 
				
			||||||
                view_light_shadow_phases.get_mut(view_light_entity).unwrap();
 | 
					                view_light_shadow_phases.get_mut(view_light_entity).unwrap();
 | 
				
			||||||
 | 
					            let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
 | 
				
			||||||
            let visible_entities = match light_entity {
 | 
					            let visible_entities = match light_entity {
 | 
				
			||||||
                LightEntity::Directional { light_entity } => directional_light_entities
 | 
					                LightEntity::Directional {
 | 
				
			||||||
 | 
					                    light_entity,
 | 
				
			||||||
 | 
					                    cascade_index,
 | 
				
			||||||
 | 
					                } => directional_light_entities
 | 
				
			||||||
                    .get(*light_entity)
 | 
					                    .get(*light_entity)
 | 
				
			||||||
                    .expect("Failed to get directional light visible entities"),
 | 
					                    .expect("Failed to get directional light visible entities")
 | 
				
			||||||
 | 
					                    .entities
 | 
				
			||||||
 | 
					                    .get(&entity)
 | 
				
			||||||
 | 
					                    .expect("Failed to get directional light visible entities for view")
 | 
				
			||||||
 | 
					                    .get(*cascade_index)
 | 
				
			||||||
 | 
					                    .expect("Failed to get directional light visible entities for cascade"),
 | 
				
			||||||
                LightEntity::Point {
 | 
					                LightEntity::Point {
 | 
				
			||||||
                    light_entity,
 | 
					                    light_entity,
 | 
				
			||||||
                    face_index,
 | 
					                    face_index,
 | 
				
			||||||
@ -1658,8 +1738,11 @@ pub fn queue_shadows(
 | 
				
			|||||||
            for entity in visible_entities.iter().copied() {
 | 
					            for entity in visible_entities.iter().copied() {
 | 
				
			||||||
                if let Ok(mesh_handle) = casting_meshes.get(entity) {
 | 
					                if let Ok(mesh_handle) = casting_meshes.get(entity) {
 | 
				
			||||||
                    if let Some(mesh) = render_meshes.get(mesh_handle) {
 | 
					                    if let Some(mesh) = render_meshes.get(mesh_handle) {
 | 
				
			||||||
                        let key =
 | 
					                        let mut key =
 | 
				
			||||||
                            ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology);
 | 
					                            ShadowPipelineKey::from_primitive_topology(mesh.primitive_topology);
 | 
				
			||||||
 | 
					                        if is_directional_light {
 | 
				
			||||||
 | 
					                            key |= ShadowPipelineKey::DEPTH_CLAMP_ORTHO;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                        let pipeline_id = pipelines.specialize(
 | 
					                        let pipeline_id = pipelines.specialize(
 | 
				
			||||||
                            &pipeline_cache,
 | 
					                            &pipeline_cache,
 | 
				
			||||||
                            &shadow_pipeline,
 | 
					                            &shadow_pipeline,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    GlobalLightMeta, GpuLights, GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver,
 | 
					    GlobalLightMeta, GpuLights, GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver,
 | 
				
			||||||
    ShadowPipeline, ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings,
 | 
					    ShadowPipeline, ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings,
 | 
				
			||||||
    CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_DIRECTIONAL_LIGHTS,
 | 
					    CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use bevy_app::Plugin;
 | 
					use bevy_app::Plugin;
 | 
				
			||||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
 | 
					use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
 | 
				
			||||||
@ -646,6 +646,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
 | 
				
			|||||||
            "MAX_DIRECTIONAL_LIGHTS".to_string(),
 | 
					            "MAX_DIRECTIONAL_LIGHTS".to_string(),
 | 
				
			||||||
            MAX_DIRECTIONAL_LIGHTS as u32,
 | 
					            MAX_DIRECTIONAL_LIGHTS as u32,
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
 | 
					        shader_defs.push(ShaderDefVal::UInt(
 | 
				
			||||||
 | 
					            "MAX_CASCADES_PER_LIGHT".to_string(),
 | 
				
			||||||
 | 
					            MAX_CASCADES_PER_LIGHT as u32,
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if layout.contains(Mesh::ATTRIBUTE_UV_0) {
 | 
					        if layout.contains(Mesh::ATTRIBUTE_UV_0) {
 | 
				
			||||||
            shader_defs.push("VERTEX_UVS".into());
 | 
					            shader_defs.push("VERTEX_UVS".into());
 | 
				
			||||||
 | 
				
			|||||||
@ -28,14 +28,23 @@ struct PointLight {
 | 
				
			|||||||
let POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32   = 1u;
 | 
					let POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32   = 1u;
 | 
				
			||||||
let POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE: u32 = 2u;
 | 
					let POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE: u32 = 2u;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct DirectionalLight {
 | 
					struct DirectionalCascade {
 | 
				
			||||||
    view_projection: mat4x4<f32>,
 | 
					    view_projection: mat4x4<f32>,
 | 
				
			||||||
 | 
					    texel_size: f32,
 | 
				
			||||||
 | 
					    far_bound: f32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					struct DirectionalLight {
 | 
				
			||||||
 | 
					    cascades: array<DirectionalCascade, #{MAX_CASCADES_PER_LIGHT}>,
 | 
				
			||||||
    color: vec4<f32>,
 | 
					    color: vec4<f32>,
 | 
				
			||||||
    direction_to_light: vec3<f32>,
 | 
					    direction_to_light: vec3<f32>,
 | 
				
			||||||
    // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
 | 
					    // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
 | 
				
			||||||
    flags: u32,
 | 
					    flags: u32,
 | 
				
			||||||
    shadow_depth_bias: f32,
 | 
					    shadow_depth_bias: f32,
 | 
				
			||||||
    shadow_normal_bias: f32,
 | 
					    shadow_normal_bias: f32,
 | 
				
			||||||
 | 
					    num_cascades: u32,
 | 
				
			||||||
 | 
					    cascades_overlap_proportion: f32,
 | 
				
			||||||
 | 
					    depth_texture_base_index: u32,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;
 | 
					let DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;
 | 
				
			||||||
 | 
				
			|||||||
@ -224,7 +224,7 @@ fn pbr(
 | 
				
			|||||||
        var shadow: f32 = 1.0;
 | 
					        var shadow: f32 = 1.0;
 | 
				
			||||||
        if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
 | 
					        if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
 | 
				
			||||||
                && (lights.directional_lights[i].flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
 | 
					                && (lights.directional_lights[i].flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
 | 
				
			||||||
            shadow = fetch_directional_shadow(i, in.world_position, in.world_normal);
 | 
					            shadow = fetch_directional_shadow(i, in.world_position, in.world_normal, view_z);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, diffuse_color);
 | 
					        let light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, diffuse_color);
 | 
				
			||||||
        light_accum = light_accum + light_contrib * shadow;
 | 
					        light_accum = light_accum + light_contrib * shadow;
 | 
				
			||||||
 | 
				
			|||||||
@ -98,15 +98,27 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: ve
 | 
				
			|||||||
    #endif
 | 
					    #endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
 | 
					fn get_cascade_index(light_id: u32, view_z: f32) -> u32 {
 | 
				
			||||||
    let light = &lights.directional_lights[light_id];
 | 
					    let light = &lights.directional_lights[light_id];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (var i: u32 = 0u; i < (*light).num_cascades; i = i + 1u) {
 | 
				
			||||||
 | 
					        if (-view_z < (*light).cascades[i].far_bound) {
 | 
				
			||||||
 | 
					            return i;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return (*light).num_cascades;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
 | 
				
			||||||
 | 
					    let light = &lights.directional_lights[light_id];
 | 
				
			||||||
 | 
					    let cascade = &(*light).cascades[cascade_index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The normal bias is scaled to the texel size.
 | 
					    // The normal bias is scaled to the texel size.
 | 
				
			||||||
    let normal_offset = (*light).shadow_normal_bias * surface_normal.xyz;
 | 
					    let normal_offset = (*light).shadow_normal_bias * (*cascade).texel_size * surface_normal.xyz;
 | 
				
			||||||
    let depth_offset = (*light).shadow_depth_bias * (*light).direction_to_light.xyz;
 | 
					    let depth_offset = (*light).shadow_depth_bias * (*light).direction_to_light.xyz;
 | 
				
			||||||
    let offset_position = vec4<f32>(frag_position.xyz + normal_offset + depth_offset, frag_position.w);
 | 
					    let offset_position = vec4<f32>(frag_position.xyz + normal_offset + depth_offset, frag_position.w);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let offset_position_clip = (*light).view_projection * offset_position;
 | 
					    let offset_position_clip = (*cascade).view_projection * offset_position;
 | 
				
			||||||
    if (offset_position_clip.w <= 0.0) {
 | 
					    if (offset_position_clip.w <= 0.0) {
 | 
				
			||||||
        return 1.0;
 | 
					        return 1.0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -127,8 +139,42 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_nor
 | 
				
			|||||||
    // NOTE: Due to non-uniform control flow above, we must use the level variant of the texture
 | 
					    // 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.
 | 
					    // sampler to avoid use of implicit derivatives causing possible undefined behavior.
 | 
				
			||||||
#ifdef NO_ARRAY_TEXTURES_SUPPORT
 | 
					#ifdef NO_ARRAY_TEXTURES_SUPPORT
 | 
				
			||||||
    return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, depth);
 | 
					    return textureSampleCompareLevel(
 | 
				
			||||||
 | 
					        directional_shadow_textures,
 | 
				
			||||||
 | 
					        directional_shadow_textures_sampler,
 | 
				
			||||||
 | 
					        light_local,
 | 
				
			||||||
 | 
					        depth
 | 
				
			||||||
 | 
					    );   
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
    return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), depth);
 | 
					    return textureSampleCompareLevel(
 | 
				
			||||||
 | 
					        directional_shadow_textures,
 | 
				
			||||||
 | 
					        directional_shadow_textures_sampler,
 | 
				
			||||||
 | 
					        light_local,
 | 
				
			||||||
 | 
					        i32((*light).depth_texture_base_index + cascade_index),
 | 
				
			||||||
 | 
					        depth
 | 
				
			||||||
 | 
					    );   
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>, view_z: f32) -> f32 {
 | 
				
			||||||
 | 
					    let light = &lights.directional_lights[light_id];
 | 
				
			||||||
 | 
					    let cascade_index = get_cascade_index(light_id, view_z);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (cascade_index >= (*light).num_cascades) {
 | 
				
			||||||
 | 
					        return 1.0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var shadow = sample_cascade(light_id, cascade_index, frag_position, surface_normal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Blend with the next cascade, if there is one.
 | 
				
			||||||
 | 
					    let next_cascade_index = cascade_index + 1u;
 | 
				
			||||||
 | 
					    if (next_cascade_index < (*light).num_cascades) {
 | 
				
			||||||
 | 
					        let this_far_bound = (*light).cascades[cascade_index].far_bound;
 | 
				
			||||||
 | 
					        let next_near_bound = (1.0 - (*light).cascades_overlap_proportion) * this_far_bound;
 | 
				
			||||||
 | 
					        if (-view_z >= next_near_bound) {
 | 
				
			||||||
 | 
					            let next_shadow = sample_cascade(light_id, next_cascade_index, frag_position, surface_normal);
 | 
				
			||||||
 | 
					            shadow = mix(shadow, next_shadow, (-view_z - next_near_bound) / (this_far_bound - next_near_bound));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return shadow;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -559,6 +559,7 @@ pub fn extract_cameras(
 | 
				
			|||||||
                ExtractedView {
 | 
					                ExtractedView {
 | 
				
			||||||
                    projection: camera.projection_matrix(),
 | 
					                    projection: camera.projection_matrix(),
 | 
				
			||||||
                    transform: *transform,
 | 
					                    transform: *transform,
 | 
				
			||||||
 | 
					                    view_projection: None,
 | 
				
			||||||
                    hdr: camera.hdr,
 | 
					                    hdr: camera.hdr,
 | 
				
			||||||
                    viewport: UVec4::new(
 | 
					                    viewport: UVec4::new(
 | 
				
			||||||
                        viewport_origin.x,
 | 
					                        viewport_origin.x,
 | 
				
			||||||
 | 
				
			|||||||
@ -279,6 +279,7 @@ impl Plugin for RenderPlugin {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        app.register_type::<color::Color>()
 | 
					        app.register_type::<color::Color>()
 | 
				
			||||||
            .register_type::<primitives::Aabb>()
 | 
					            .register_type::<primitives::Aabb>()
 | 
				
			||||||
 | 
					            .register_type::<primitives::CascadesFrusta>()
 | 
				
			||||||
            .register_type::<primitives::CubemapFrusta>()
 | 
					            .register_type::<primitives::CubemapFrusta>()
 | 
				
			||||||
            .register_type::<primitives::Frustum>();
 | 
					            .register_type::<primitives::Frustum>();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
use bevy_ecs::{component::Component, reflect::ReflectComponent};
 | 
					use bevy_ecs::{component::Component, prelude::Entity, reflect::ReflectComponent};
 | 
				
			||||||
use bevy_math::{Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles};
 | 
					use bevy_math::{Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles};
 | 
				
			||||||
use bevy_reflect::Reflect;
 | 
					use bevy_reflect::Reflect;
 | 
				
			||||||
 | 
					use bevy_utils::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// An Axis-Aligned Bounding Box
 | 
					/// An Axis-Aligned Bounding Box
 | 
				
			||||||
#[derive(Component, Clone, Debug, Default, Reflect)]
 | 
					#[derive(Component, Clone, Debug, Default, Reflect)]
 | 
				
			||||||
@ -134,17 +135,33 @@ pub struct Frustum {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Frustum {
 | 
					impl Frustum {
 | 
				
			||||||
    // NOTE: This approach of extracting the frustum planes from the view
 | 
					    /// Returns a frustum derived from `view_projection`.
 | 
				
			||||||
    // projection matrix is from Foundations of Game Engine Development 2
 | 
					 | 
				
			||||||
    // Rendering by Lengyel. Slight modification has been made for when
 | 
					 | 
				
			||||||
    // the far plane is infinite but we still want to cull to a far plane.
 | 
					 | 
				
			||||||
    #[inline]
 | 
					    #[inline]
 | 
				
			||||||
    pub fn from_view_projection(
 | 
					    pub fn from_view_projection(view_projection: &Mat4) -> Self {
 | 
				
			||||||
 | 
					        let mut frustum = Frustum::from_view_projection_no_far(view_projection);
 | 
				
			||||||
 | 
					        frustum.planes[5] = Plane::new(view_projection.row(2));
 | 
				
			||||||
 | 
					        frustum
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns a frustum derived from `view_projection`, but with a custom
 | 
				
			||||||
 | 
					    /// far plane.
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    pub fn from_view_projection_custom_far(
 | 
				
			||||||
        view_projection: &Mat4,
 | 
					        view_projection: &Mat4,
 | 
				
			||||||
        view_translation: &Vec3,
 | 
					        view_translation: &Vec3,
 | 
				
			||||||
        view_backward: &Vec3,
 | 
					        view_backward: &Vec3,
 | 
				
			||||||
        far: f32,
 | 
					        far: f32,
 | 
				
			||||||
    ) -> Self {
 | 
					    ) -> Self {
 | 
				
			||||||
 | 
					        let mut frustum = Frustum::from_view_projection_no_far(view_projection);
 | 
				
			||||||
 | 
					        let far_center = *view_translation - far * *view_backward;
 | 
				
			||||||
 | 
					        frustum.planes[5] = Plane::new(view_backward.extend(-view_backward.dot(far_center)));
 | 
				
			||||||
 | 
					        frustum
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // NOTE: This approach of extracting the frustum planes from the view
 | 
				
			||||||
 | 
					    // projection matrix is from Foundations of Game Engine Development 2
 | 
				
			||||||
 | 
					    // Rendering by Lengyel.
 | 
				
			||||||
 | 
					    fn from_view_projection_no_far(view_projection: &Mat4) -> Self {
 | 
				
			||||||
        let row3 = view_projection.row(3);
 | 
					        let row3 = view_projection.row(3);
 | 
				
			||||||
        let mut planes = [Plane::default(); 6];
 | 
					        let mut planes = [Plane::default(); 6];
 | 
				
			||||||
        for (i, plane) in planes.iter_mut().enumerate().take(5) {
 | 
					        for (i, plane) in planes.iter_mut().enumerate().take(5) {
 | 
				
			||||||
@ -155,8 +172,6 @@ impl Frustum {
 | 
				
			|||||||
                row3 - row
 | 
					                row3 - row
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let far_center = *view_translation - far * *view_backward;
 | 
					 | 
				
			||||||
        planes[5] = Plane::new(view_backward.extend(-view_backward.dot(far_center)));
 | 
					 | 
				
			||||||
        Self { planes }
 | 
					        Self { planes }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -173,7 +188,13 @@ impl Frustum {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[inline]
 | 
					    #[inline]
 | 
				
			||||||
    pub fn intersects_obb(&self, aabb: &Aabb, model_to_world: &Mat4, intersect_far: bool) -> bool {
 | 
					    pub fn intersects_obb(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        aabb: &Aabb,
 | 
				
			||||||
 | 
					        model_to_world: &Mat4,
 | 
				
			||||||
 | 
					        intersect_near: bool,
 | 
				
			||||||
 | 
					        intersect_far: bool,
 | 
				
			||||||
 | 
					    ) -> bool {
 | 
				
			||||||
        let aabb_center_world = model_to_world.transform_point3a(aabb.center).extend(1.0);
 | 
					        let aabb_center_world = model_to_world.transform_point3a(aabb.center).extend(1.0);
 | 
				
			||||||
        let axes = [
 | 
					        let axes = [
 | 
				
			||||||
            Vec3A::from(model_to_world.x_axis),
 | 
					            Vec3A::from(model_to_world.x_axis),
 | 
				
			||||||
@ -181,8 +202,13 @@ impl Frustum {
 | 
				
			|||||||
            Vec3A::from(model_to_world.z_axis),
 | 
					            Vec3A::from(model_to_world.z_axis),
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let max = if intersect_far { 6 } else { 5 };
 | 
					        for (idx, plane) in self.planes.into_iter().enumerate() {
 | 
				
			||||||
        for plane in &self.planes[..max] {
 | 
					            if idx == 4 && !intersect_near {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if idx == 5 && !intersect_far {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            let p_normal = Vec3A::from(plane.normal_d());
 | 
					            let p_normal = Vec3A::from(plane.normal_d());
 | 
				
			||||||
            let relative_radius = aabb.relative_radius(&p_normal, &axes);
 | 
					            let relative_radius = aabb.relative_radius(&p_normal, &axes);
 | 
				
			||||||
            if plane.normal_d().dot(aabb_center_world) + relative_radius <= 0.0 {
 | 
					            if plane.normal_d().dot(aabb_center_world) + relative_radius <= 0.0 {
 | 
				
			||||||
@ -209,6 +235,13 @@ impl CubemapFrusta {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Debug, Default, Reflect)]
 | 
				
			||||||
 | 
					#[reflect(Component)]
 | 
				
			||||||
 | 
					pub struct CascadesFrusta {
 | 
				
			||||||
 | 
					    #[reflect(ignore)]
 | 
				
			||||||
 | 
					    pub frusta: HashMap<Entity, Vec<Frustum>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
 | 
				
			|||||||
@ -91,6 +91,10 @@ impl Msaa {
 | 
				
			|||||||
pub struct ExtractedView {
 | 
					pub struct ExtractedView {
 | 
				
			||||||
    pub projection: Mat4,
 | 
					    pub projection: Mat4,
 | 
				
			||||||
    pub transform: GlobalTransform,
 | 
					    pub transform: GlobalTransform,
 | 
				
			||||||
 | 
					    // The view-projection matrix. When provided it is used instead of deriving it from
 | 
				
			||||||
 | 
					    // `projection` and `transform` fields, which can be helpful in cases where numerical
 | 
				
			||||||
 | 
					    // stability matters and there is a more direct way to derive the view-projection matrix.
 | 
				
			||||||
 | 
					    pub view_projection: Option<Mat4>,
 | 
				
			||||||
    pub hdr: bool,
 | 
					    pub hdr: bool,
 | 
				
			||||||
    // uvec4(origin.x, origin.y, width, height)
 | 
					    // uvec4(origin.x, origin.y, width, height)
 | 
				
			||||||
    pub viewport: UVec4,
 | 
					    pub viewport: UVec4,
 | 
				
			||||||
@ -251,7 +255,9 @@ fn prepare_view_uniforms(
 | 
				
			|||||||
        let inverse_view = view.inverse();
 | 
					        let inverse_view = view.inverse();
 | 
				
			||||||
        let view_uniforms = ViewUniformOffset {
 | 
					        let view_uniforms = ViewUniformOffset {
 | 
				
			||||||
            offset: view_uniforms.uniforms.push(ViewUniform {
 | 
					            offset: view_uniforms.uniforms.push(ViewUniform {
 | 
				
			||||||
                view_proj: projection * inverse_view,
 | 
					                view_proj: camera
 | 
				
			||||||
 | 
					                    .view_projection
 | 
				
			||||||
 | 
					                    .unwrap_or_else(|| projection * inverse_view),
 | 
				
			||||||
                inverse_view_proj: view * inverse_projection,
 | 
					                inverse_view_proj: view * inverse_projection,
 | 
				
			||||||
                view,
 | 
					                view,
 | 
				
			||||||
                inverse_view,
 | 
					                inverse_view,
 | 
				
			||||||
 | 
				
			|||||||
@ -281,7 +281,7 @@ pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
 | 
				
			|||||||
    for (transform, projection, mut frustum) in &mut views {
 | 
					    for (transform, projection, mut frustum) in &mut views {
 | 
				
			||||||
        let view_projection =
 | 
					        let view_projection =
 | 
				
			||||||
            projection.get_projection_matrix() * transform.compute_matrix().inverse();
 | 
					            projection.get_projection_matrix() * transform.compute_matrix().inverse();
 | 
				
			||||||
        *frustum = Frustum::from_view_projection(
 | 
					        *frustum = Frustum::from_view_projection_custom_far(
 | 
				
			||||||
            &view_projection,
 | 
					            &view_projection,
 | 
				
			||||||
            &transform.translation(),
 | 
					            &transform.translation(),
 | 
				
			||||||
            &transform.back(),
 | 
					            &transform.back(),
 | 
				
			||||||
@ -407,7 +407,7 @@ pub fn check_visibility(
 | 
				
			|||||||
                        return;
 | 
					                        return;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    // If we have an aabb, do aabb-based frustum culling
 | 
					                    // If we have an aabb, do aabb-based frustum culling
 | 
				
			||||||
                    if !frustum.intersects_obb(model_aabb, &model, false) {
 | 
					                    if !frustum.intersects_obb(model_aabb, &model, true, false) {
 | 
				
			||||||
                        return;
 | 
					                        return;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -275,6 +275,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
 | 
				
			|||||||
                        0.0,
 | 
					                        0.0,
 | 
				
			||||||
                        UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET,
 | 
					                        UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 | 
					                    view_projection: None,
 | 
				
			||||||
                    hdr: camera.hdr,
 | 
					                    hdr: camera.hdr,
 | 
				
			||||||
                    viewport: UVec4::new(
 | 
					                    viewport: UVec4::new(
 | 
				
			||||||
                        physical_origin.x,
 | 
					                        physical_origin.x,
 | 
				
			||||||
 | 
				
			|||||||
@ -70,18 +70,8 @@ fn setup(
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // light
 | 
					    // light
 | 
				
			||||||
    const HALF_SIZE: f32 = 2.0;
 | 
					 | 
				
			||||||
    commands.spawn(DirectionalLightBundle {
 | 
					    commands.spawn(DirectionalLightBundle {
 | 
				
			||||||
        directional_light: DirectionalLight {
 | 
					        directional_light: DirectionalLight {
 | 
				
			||||||
            shadow_projection: OrthographicProjection {
 | 
					 | 
				
			||||||
                left: -HALF_SIZE,
 | 
					 | 
				
			||||||
                right: HALF_SIZE,
 | 
					 | 
				
			||||||
                bottom: -HALF_SIZE,
 | 
					 | 
				
			||||||
                top: HALF_SIZE,
 | 
					 | 
				
			||||||
                near: -10.0 * HALF_SIZE,
 | 
					 | 
				
			||||||
                far: 10.0 * HALF_SIZE,
 | 
					 | 
				
			||||||
                ..default()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            shadows_enabled: true,
 | 
					            shadows_enabled: true,
 | 
				
			||||||
            ..default()
 | 
					            ..default()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use std::f32::consts::PI;
 | 
					use std::f32::consts::PI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use bevy::prelude::*;
 | 
					use bevy::{pbr::CascadeShadowConfig, prelude::*};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
    App::new()
 | 
					    App::new()
 | 
				
			||||||
@ -186,19 +186,8 @@ fn setup(
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // directional 'sun' light
 | 
					    // directional 'sun' light
 | 
				
			||||||
    const HALF_SIZE: f32 = 10.0;
 | 
					 | 
				
			||||||
    commands.spawn(DirectionalLightBundle {
 | 
					    commands.spawn(DirectionalLightBundle {
 | 
				
			||||||
        directional_light: DirectionalLight {
 | 
					        directional_light: DirectionalLight {
 | 
				
			||||||
            // Configure the projection to better fit the scene
 | 
					 | 
				
			||||||
            shadow_projection: OrthographicProjection {
 | 
					 | 
				
			||||||
                left: -HALF_SIZE,
 | 
					 | 
				
			||||||
                right: HALF_SIZE,
 | 
					 | 
				
			||||||
                bottom: -HALF_SIZE,
 | 
					 | 
				
			||||||
                top: HALF_SIZE,
 | 
					 | 
				
			||||||
                near: -10.0 * HALF_SIZE,
 | 
					 | 
				
			||||||
                far: 10.0 * HALF_SIZE,
 | 
					 | 
				
			||||||
                ..default()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            shadows_enabled: true,
 | 
					            shadows_enabled: true,
 | 
				
			||||||
            ..default()
 | 
					            ..default()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -207,6 +196,10 @@ fn setup(
 | 
				
			|||||||
            rotation: Quat::from_rotation_x(-PI / 4.),
 | 
					            rotation: Quat::from_rotation_x(-PI / 4.),
 | 
				
			||||||
            ..default()
 | 
					            ..default()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        // The default cascade config is designed to handle large scenes.
 | 
				
			||||||
 | 
					        // As this example has a much smaller world, we can tighten the shadow
 | 
				
			||||||
 | 
					        // far bound for better visual quality.
 | 
				
			||||||
 | 
					        cascade_shadow_config: CascadeShadowConfig::new(4, 5.0, 30.0, 0.2),
 | 
				
			||||||
        ..default()
 | 
					        ..default()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use std::f32::consts::*;
 | 
					use std::f32::consts::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use bevy::prelude::*;
 | 
					use bevy::{pbr::CascadeShadowConfig, prelude::*};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
    App::new()
 | 
					    App::new()
 | 
				
			||||||
@ -21,21 +21,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
 | 
				
			|||||||
        transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
 | 
					        transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
 | 
				
			||||||
        ..default()
 | 
					        ..default()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    const HALF_SIZE: f32 = 1.0;
 | 
					 | 
				
			||||||
    commands.spawn(DirectionalLightBundle {
 | 
					    commands.spawn(DirectionalLightBundle {
 | 
				
			||||||
        directional_light: DirectionalLight {
 | 
					        directional_light: DirectionalLight {
 | 
				
			||||||
            shadow_projection: OrthographicProjection {
 | 
					 | 
				
			||||||
                left: -HALF_SIZE,
 | 
					 | 
				
			||||||
                right: HALF_SIZE,
 | 
					 | 
				
			||||||
                bottom: -HALF_SIZE,
 | 
					 | 
				
			||||||
                top: HALF_SIZE,
 | 
					 | 
				
			||||||
                near: -10.0 * HALF_SIZE,
 | 
					 | 
				
			||||||
                far: 10.0 * HALF_SIZE,
 | 
					 | 
				
			||||||
                ..default()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            shadows_enabled: true,
 | 
					            shadows_enabled: true,
 | 
				
			||||||
            ..default()
 | 
					            ..default()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        // This is a relatively small scene, so use tighter shadow
 | 
				
			||||||
 | 
					        // cascade bounds than the default for better quality.
 | 
				
			||||||
 | 
					        cascade_shadow_config: CascadeShadowConfig::new(1, 1.1, 1.5, 0.3),
 | 
				
			||||||
        ..default()
 | 
					        ..default()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    commands.spawn(SceneBundle {
 | 
					    commands.spawn(SceneBundle {
 | 
				
			||||||
 | 
				
			|||||||
@ -69,15 +69,6 @@ fn setup(
 | 
				
			|||||||
    commands.spawn(DirectionalLightBundle {
 | 
					    commands.spawn(DirectionalLightBundle {
 | 
				
			||||||
        directional_light: DirectionalLight {
 | 
					        directional_light: DirectionalLight {
 | 
				
			||||||
            illuminance: 100000.0,
 | 
					            illuminance: 100000.0,
 | 
				
			||||||
            shadow_projection: OrthographicProjection {
 | 
					 | 
				
			||||||
                left: -0.35,
 | 
					 | 
				
			||||||
                right: 500.35,
 | 
					 | 
				
			||||||
                bottom: -0.1,
 | 
					 | 
				
			||||||
                top: 5.0,
 | 
					 | 
				
			||||||
                near: -5.0,
 | 
					 | 
				
			||||||
                far: 5.0,
 | 
					 | 
				
			||||||
                ..default()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            shadow_depth_bias: 0.0,
 | 
					            shadow_depth_bias: 0.0,
 | 
				
			||||||
            shadow_normal_bias: 0.0,
 | 
					            shadow_normal_bias: 0.0,
 | 
				
			||||||
            shadows_enabled: true,
 | 
					            shadows_enabled: true,
 | 
				
			||||||
 | 
				
			|||||||
@ -100,15 +100,6 @@ fn setup(
 | 
				
			|||||||
    commands.spawn(DirectionalLightBundle {
 | 
					    commands.spawn(DirectionalLightBundle {
 | 
				
			||||||
        directional_light: DirectionalLight {
 | 
					        directional_light: DirectionalLight {
 | 
				
			||||||
            illuminance: 100000.0,
 | 
					            illuminance: 100000.0,
 | 
				
			||||||
            shadow_projection: OrthographicProjection {
 | 
					 | 
				
			||||||
                left: -10.0,
 | 
					 | 
				
			||||||
                right: 10.0,
 | 
					 | 
				
			||||||
                bottom: -10.0,
 | 
					 | 
				
			||||||
                top: 10.0,
 | 
					 | 
				
			||||||
                near: -50.0,
 | 
					 | 
				
			||||||
                far: 50.0,
 | 
					 | 
				
			||||||
                ..default()
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            shadows_enabled: true,
 | 
					            shadows_enabled: true,
 | 
				
			||||||
            ..default()
 | 
					            ..default()
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
				
			|||||||
@ -132,26 +132,9 @@ fn setup_scene_after_load(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Spawn a default light if the scene does not have one
 | 
					        // Spawn a default light if the scene does not have one
 | 
				
			||||||
        if !scene_handle.has_light {
 | 
					        if !scene_handle.has_light {
 | 
				
			||||||
            let sphere = Sphere {
 | 
					 | 
				
			||||||
                center: aabb.center,
 | 
					 | 
				
			||||||
                radius: aabb.half_extents.length(),
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            let aabb = Aabb::from(sphere);
 | 
					 | 
				
			||||||
            let min = aabb.min();
 | 
					 | 
				
			||||||
            let max = aabb.max();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            info!("Spawning a directional light");
 | 
					            info!("Spawning a directional light");
 | 
				
			||||||
            commands.spawn(DirectionalLightBundle {
 | 
					            commands.spawn(DirectionalLightBundle {
 | 
				
			||||||
                directional_light: DirectionalLight {
 | 
					                directional_light: DirectionalLight {
 | 
				
			||||||
                    shadow_projection: OrthographicProjection {
 | 
					 | 
				
			||||||
                        left: min.x,
 | 
					 | 
				
			||||||
                        right: max.x,
 | 
					 | 
				
			||||||
                        bottom: min.y,
 | 
					 | 
				
			||||||
                        top: max.y,
 | 
					 | 
				
			||||||
                        near: min.z,
 | 
					 | 
				
			||||||
                        far: max.z,
 | 
					 | 
				
			||||||
                        ..default()
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    shadows_enabled: false,
 | 
					                    shadows_enabled: false,
 | 
				
			||||||
                    ..default()
 | 
					                    ..default()
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
				
			|||||||
@ -44,9 +44,6 @@ Scene Controls:
 | 
				
			|||||||
    L           - animate light direction
 | 
					    L           - animate light direction
 | 
				
			||||||
    U           - toggle shadows
 | 
					    U           - toggle shadows
 | 
				
			||||||
    C           - cycle through the camera controller and any cameras loaded from the scene
 | 
					    C           - cycle through the camera controller and any cameras loaded from the scene
 | 
				
			||||||
    5/6         - decrease/increase shadow projection width
 | 
					 | 
				
			||||||
    7/8         - decrease/increase shadow projection height
 | 
					 | 
				
			||||||
    9/0         - decrease/increase shadow projection near/far
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Space       - Play/Pause animation
 | 
					    Space       - Play/Pause animation
 | 
				
			||||||
    Enter       - Cycle through animations
 | 
					    Enter       - Cycle through animations
 | 
				
			||||||
@ -198,35 +195,13 @@ fn keyboard_animation_control(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SCALE_STEP: f32 = 0.1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn update_lights(
 | 
					fn update_lights(
 | 
				
			||||||
    key_input: Res<Input<KeyCode>>,
 | 
					    key_input: Res<Input<KeyCode>>,
 | 
				
			||||||
    time: Res<Time>,
 | 
					    time: Res<Time>,
 | 
				
			||||||
    mut query: Query<(&mut Transform, &mut DirectionalLight)>,
 | 
					    mut query: Query<(&mut Transform, &mut DirectionalLight)>,
 | 
				
			||||||
    mut animate_directional_light: Local<bool>,
 | 
					    mut animate_directional_light: Local<bool>,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    let mut projection_adjustment = Vec3::ONE;
 | 
					 | 
				
			||||||
    if key_input.just_pressed(KeyCode::Key5) {
 | 
					 | 
				
			||||||
        projection_adjustment.x -= SCALE_STEP;
 | 
					 | 
				
			||||||
    } else if key_input.just_pressed(KeyCode::Key6) {
 | 
					 | 
				
			||||||
        projection_adjustment.x += SCALE_STEP;
 | 
					 | 
				
			||||||
    } else if key_input.just_pressed(KeyCode::Key7) {
 | 
					 | 
				
			||||||
        projection_adjustment.y -= SCALE_STEP;
 | 
					 | 
				
			||||||
    } else if key_input.just_pressed(KeyCode::Key8) {
 | 
					 | 
				
			||||||
        projection_adjustment.y += SCALE_STEP;
 | 
					 | 
				
			||||||
    } else if key_input.just_pressed(KeyCode::Key9) {
 | 
					 | 
				
			||||||
        projection_adjustment.z -= SCALE_STEP;
 | 
					 | 
				
			||||||
    } else if key_input.just_pressed(KeyCode::Key0) {
 | 
					 | 
				
			||||||
        projection_adjustment.z += SCALE_STEP;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    for (_, mut light) in &mut query {
 | 
					    for (_, mut light) in &mut query {
 | 
				
			||||||
        light.shadow_projection.left *= projection_adjustment.x;
 | 
					 | 
				
			||||||
        light.shadow_projection.right *= projection_adjustment.x;
 | 
					 | 
				
			||||||
        light.shadow_projection.bottom *= projection_adjustment.y;
 | 
					 | 
				
			||||||
        light.shadow_projection.top *= projection_adjustment.y;
 | 
					 | 
				
			||||||
        light.shadow_projection.near *= projection_adjustment.z;
 | 
					 | 
				
			||||||
        light.shadow_projection.far *= projection_adjustment.z;
 | 
					 | 
				
			||||||
        if key_input.just_pressed(KeyCode::U) {
 | 
					        if key_input.just_pressed(KeyCode::U) {
 | 
				
			||||||
            light.shadows_enabled = !light.shadows_enabled;
 | 
					            light.shadows_enabled = !light.shadows_enabled;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user