Skip to content

Commit

Permalink
Cascaded shadow maps. (#7064)
Browse files Browse the repository at this point in the history
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
![image](https://user-images.githubusercontent.com/1222141/210061203-bbd965a4-8d11-4cec-9a88-67fc59d0819f.png)

After
![image](https://user-images.githubusercontent.com/1222141/210061334-2ff15334-e6d7-4a31-9314-f34a7805cac6.png)


## 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.
  • Loading branch information
danchia committed Jan 25, 2023
1 parent f27e9b2 commit c3a4682
Show file tree
Hide file tree
Showing 23 changed files with 672 additions and 284 deletions.
2 changes: 1 addition & 1 deletion crates/bevy_core_pipeline/src/core_2d/camera_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl Camera2dBundle {
let transform = Transform::from_xyz(0.0, 0.0, far - 0.1);
let view_projection =
projection.get_projection_matrix() * transform.compute_matrix().inverse();
let frustum = Frustum::from_view_projection(
let frustum = Frustum::from_view_projection_custom_far(
&view_projection,
&transform.translation,
&transform.back(),
Expand Down
24 changes: 19 additions & 5 deletions crates/bevy_pbr/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -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_ecs::{bundle::Bundle, component::Component, reflect::ReflectComponent};
use bevy_ecs::{bundle::Bundle, component::Component, prelude::Entity, reflect::ReflectComponent};
use bevy_reflect::Reflect;
use bevy_render::{
mesh::Mesh,
primitives::{CubemapFrusta, Frustum},
primitives::{CascadesFrusta, CubemapFrusta, Frustum},
view::{ComputedVisibility, Visibility, VisibleEntities},
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::HashMap;

/// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`].
pub type PbrBundle = MaterialMeshBundle<StandardMaterial>;
Expand Down Expand Up @@ -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.
#[derive(Debug, Bundle, Default)]
pub struct PointLightBundle {
Expand Down Expand Up @@ -95,8 +107,10 @@ pub struct SpotLightBundle {
#[derive(Debug, Bundle, Default)]
pub struct DirectionalLightBundle {
pub directional_light: DirectionalLight,
pub frustum: Frustum,
pub visible_entities: VisibleEntities,
pub frusta: CascadesFrusta,
pub cascades: Cascades,
pub cascade_shadow_config: CascadeShadowConfig,
pub visible_entities: CascadesVisibleEntities,
pub transform: Transform,
pub global_transform: GlobalTransform,
/// Enables or disables the light
Expand Down
24 changes: 17 additions & 7 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,20 @@ impl Plugin for PbrPlugin {
Shader::from_wgsl
);

app.register_type::<CubemapVisibleEntities>()
.register_type::<DirectionalLight>()
.register_type::<PointLight>()
.register_type::<SpotLight>()
.register_asset_reflect::<StandardMaterial>()
app.register_asset_reflect::<StandardMaterial>()
.register_type::<AmbientLight>()
.register_type::<DirectionalLightShadowMap>()
.register_type::<CascadeShadowConfig>()
.register_type::<Cascades>()
.register_type::<CascadesVisibleEntities>()
.register_type::<ClusterConfig>()
.register_type::<ClusterZConfig>()
.register_type::<ClusterFarZMode>()
.register_type::<ClusterZConfig>()
.register_type::<CubemapVisibleEntities>()
.register_type::<DirectionalLight>()
.register_type::<DirectionalLightShadowMap>()
.register_type::<PointLight>()
.register_type::<PointLightShadowMap>()
.register_type::<SpotLight>()
.add_plugin(MeshRenderPlugin)
.add_plugin(MaterialPlugin::<StandardMaterial> {
prepass_enabled: self.prepass_enabled,
Expand Down Expand Up @@ -183,13 +186,20 @@ impl Plugin for PbrPlugin {
.after(CameraUpdateSystem)
.after(ModifiesWindows),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_directional_light_cascades
.label(SimulationLightSystems::UpdateDirectionalLightCascades)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_directional_light_frusta
.label(SimulationLightSystems::UpdateLightFrusta)
// This must run after CheckVisibility because it relies on ComputedVisibility::is_visible()
.after(VisibilitySystems::CheckVisibility)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateDirectionalLightCascades)
// We assume that no entity will be both a directional light and a spot light,
// so these systems will run independently of one another.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
Expand Down
Loading

0 comments on commit c3a4682

Please sign in to comment.