Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,19 @@ description = "Showcases different blend modes"
category = "3D Rendering"
wasm = true

[[example]]
name = "contact_shadows"
path = "examples/3d/contact_shadows.rs"
# Causes an ICE on docs.rs
doc-scrape-examples = false
required-features = ["bluenoise_texture"]

[package.metadata.example.contact_shadows]
name = "Contact Shadows"
description = "Showcases how contact shadows add shadow detail"
category = "3D Rendering"
wasm = true

[[example]]
name = "lighting"
path = "examples/3d/lighting.rs"
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_light/src/cascade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ pub struct Cascade {

pub fn clear_directional_light_cascades(mut lights: Query<(&DirectionalLight, &mut Cascades)>) {
for (directional_light, mut cascades) in lights.iter_mut() {
if !directional_light.shadows_enabled {
if !directional_light.shadow_maps_enabled {
continue;
}
cascades.cascades.clear();
Expand Down Expand Up @@ -214,7 +214,7 @@ pub fn build_directional_light_cascades(
.collect::<Vec<_>>();

for (transform, directional_light, cascades_config, mut cascades) in &mut lights {
if !directional_light.shadows_enabled {
if !directional_light.shadow_maps_enabled {
continue;
}

Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_light/src/cluster/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub enum ClusterableObjectType {
/// Whether shadows are enabled for this point light.
///
/// This is used for sorting the light list.
shadows_enabled: bool,
shadow_maps_enabled: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need a migration guide


/// Whether this light interacts with volumetrics.
///
Expand All @@ -73,7 +73,7 @@ pub enum ClusterableObjectType {
/// Whether shadows are enabled for this spot light.
///
/// This is used for sorting the light list.
shadows_enabled: bool,
shadow_maps_enabled: bool,

/// Whether this light interacts with volumetrics.
///
Expand Down Expand Up @@ -105,14 +105,14 @@ impl ClusterableObjectType {
pub fn ordering(&self) -> (u8, bool, bool) {
match *self {
ClusterableObjectType::PointLight {
shadows_enabled,
shadow_maps_enabled,
volumetric,
} => (0, !shadows_enabled, !volumetric),
} => (0, !shadow_maps_enabled, !volumetric),
ClusterableObjectType::SpotLight {
shadows_enabled,
shadow_maps_enabled,
volumetric,
..
} => (1, !shadows_enabled, !volumetric),
} => (1, !shadow_maps_enabled, !volumetric),
ClusterableObjectType::ReflectionProbe => (2, false, false),
ClusterableObjectType::IrradianceVolume => (3, false, false),
ClusterableObjectType::Decal => (4, false, false),
Expand Down Expand Up @@ -178,7 +178,7 @@ pub(crate) fn assign_objects_to_clusters(
transform: GlobalTransform::from_translation(transform.translation()),
range: point_light.range,
object_type: ClusterableObjectType::PointLight {
shadows_enabled: point_light.shadows_enabled,
shadow_maps_enabled: point_light.shadow_maps_enabled,
volumetric: volumetric.is_some(),
},
render_layers: maybe_layers.unwrap_or_default().clone(),
Expand All @@ -198,7 +198,7 @@ pub(crate) fn assign_objects_to_clusters(
range: spot_light.range,
object_type: ClusterableObjectType::SpotLight {
outer_angle: spot_light.outer_angle,
shadows_enabled: spot_light.shadows_enabled,
shadow_maps_enabled: spot_light.shadow_maps_enabled,
volumetric: volumetric.is_some(),
},
render_layers: maybe_layers.unwrap_or_default().clone(),
Expand Down
12 changes: 8 additions & 4 deletions crates/bevy_light/src/directional_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use super::{
///
/// ## Shadows
///
/// To enable shadows, set the `shadows_enabled` property to `true`.
/// To enable shadows, set the `shadow_maps_enabled` property to `true`.
///
/// 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).
///
Expand Down Expand Up @@ -86,7 +86,10 @@ pub struct DirectionalLight {
/// Note that shadows are rather expensive and become more so with every
/// light that casts them. In general, it's best to aggressively limit the
/// number of lights with shadows enabled to one or two at most.
pub shadows_enabled: bool,
pub shadow_maps_enabled: bool,

/// Whether this light casts contact shadows.
pub contact_shadows_enabled: bool,

/// Whether soft shadows are enabled, and if so, the size of the light.
///
Expand Down Expand Up @@ -142,7 +145,8 @@ impl Default for DirectionalLight {
DirectionalLight {
color: Color::WHITE,
illuminance: light_consts::lux::AMBIENT_DAYLIGHT,
shadows_enabled: false,
shadow_maps_enabled: false,
contact_shadows_enabled: false,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
affects_lightmapped_mesh_diffuse: true,
Expand Down Expand Up @@ -221,7 +225,7 @@ pub fn update_directional_light_frusta(
// 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
// not needed.
if !directional_light.shadows_enabled || !visibility.get() {
if !directional_light.shadow_maps_enabled || !visibility.get() {
continue;
}

Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_light/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ pub fn check_dir_light_mesh_visibility(
}

// NOTE: If shadow mapping is disabled for the light then it must have no visible entities
if !directional_light.shadows_enabled || !light_view_visibility.get() {
if !directional_light.shadow_maps_enabled || !light_view_visibility.get() {
continue;
}

Expand Down Expand Up @@ -524,7 +524,7 @@ pub fn check_point_light_mesh_visibility(
}

// NOTE: If shadow mapping is disabled for the light then it must have no visible entities
if !point_light.shadows_enabled {
if !point_light.shadow_maps_enabled {
continue;
}

Expand Down Expand Up @@ -613,7 +613,7 @@ pub fn check_point_light_mesh_visibility(
visible_entities.clear();

// NOTE: If shadow mapping is disabled for the light then it must have no visible entities
if !point_light.shadows_enabled {
if !point_light.shadow_maps_enabled {
continue;
}

Expand Down
12 changes: 8 additions & 4 deletions crates/bevy_light/src/point_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::{
///
/// ## Shadows
///
/// To enable shadows, set the `shadows_enabled` property to `true`.
/// To enable shadows, set the `shadow_maps_enabled` property to `true`.
///
/// To control the resolution of the shadow maps, use the [`PointLightShadowMap`] resource.
#[derive(Component, Debug, Clone, Copy, Reflect)]
Expand Down Expand Up @@ -70,7 +70,10 @@ pub struct PointLight {
pub radius: f32,

/// Whether this light casts shadows.
pub shadows_enabled: bool,
pub shadow_maps_enabled: bool,

/// Whether this light casts contact shadows.
pub contact_shadows_enabled: bool,

/// Whether soft shadows are enabled.
///
Expand Down Expand Up @@ -132,7 +135,8 @@ impl Default for PointLight {
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT,
range: 20.0,
radius: 0.0,
shadows_enabled: false,
shadow_maps_enabled: false,
contact_shadows_enabled: false,
affects_lightmapped_mesh_diffuse: true,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
Expand Down Expand Up @@ -214,7 +218,7 @@ pub fn update_point_light_frusta(
// not needed.
// Also, if the light is not relevant for any cluster, it will not be in the
// global lights set and so there is no need to update its frusta.
if !point_light.shadows_enabled || !global_lights.entities.contains(&entity) {
if !point_light.shadow_maps_enabled || !global_lights.entities.contains(&entity) {
continue;
}

Expand Down
11 changes: 8 additions & 3 deletions crates/bevy_light/src/spot_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ pub struct SpotLight {
/// Note that shadows are rather expensive and become more so with every
/// light that casts them. In general, it's best to aggressively limit the
/// number of lights with shadows enabled to one or two at most.
pub shadows_enabled: bool,
pub shadow_maps_enabled: bool,

/// Whether this light casts contact shadows. Cameras must also have the `ContactShadows`
/// component.
pub contact_shadows_enabled: bool,

/// Whether soft shadows are enabled.
///
Expand Down Expand Up @@ -142,7 +146,8 @@ impl Default for SpotLight {
intensity: 1_000_000.0,
range: 20.0,
radius: 0.0,
shadows_enabled: false,
shadow_maps_enabled: false,
contact_shadows_enabled: false,
affects_lightmapped_mesh_diffuse: true,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
Expand Down Expand Up @@ -214,7 +219,7 @@ pub fn update_spot_light_frusta(
// not needed.
// Also, if the light is not relevant for any cluster, it will not be in the
// global lights set and so there is no need to update its frusta.
if !spot_light.shadows_enabled || !global_lights.entities.contains(&entity) {
if !spot_light.shadow_maps_enabled || !global_lights.entities.contains(&entity) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_light/src/volumetric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bevy_reflect::prelude::*;
use bevy_transform::components::Transform;

/// Add this component to a [`DirectionalLight`](crate::DirectionalLight) with a shadow map
/// (`shadows_enabled: true`) to make volumetric fog interact with it.
/// (`shadow_maps_enabled: true`) to make volumetric fog interact with it.
///
/// This allows the light to generate light shafts/god rays.
#[derive(Clone, Copy, Component, Default, Debug, Reflect)]
Expand Down
140 changes: 140 additions & 0 deletions crates/bevy_pbr/src/contact_shadows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//! Contact shadows implemented via screenspace raymarching.

use bevy_app::{App, Plugin};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
entity::Entity,
query::{QueryItem, With},
reflect::ReflectComponent,
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Commands, Query, Res, ResMut},
};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_resource::{DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ExtractedView,
Render, RenderApp, RenderSystems,
};
use bevy_utils::default;

/// Enables contact shadows for a camera.
pub struct ContactShadowsPlugin;

/// Add this component to a camera to enable contact shadows.
///
/// Contact shadows are a screen-space technique that adds small-scale shadows
/// in areas where traditional shadow maps may lack detail, such as where
/// objects touch the ground.
///
/// This can be used in forward or deferred rendering, but the depth prepass is required.
#[derive(Clone, Copy, Component, Reflect)]
#[reflect(Component, Default, Clone)]
#[require(bevy_core_pipeline::prepass::DepthPrepass)]
pub struct ContactShadows {
/// The number of steps to be taken at regular intervals to find an initial
/// intersection.
pub linear_steps: u32,
/// When marching the depth buffer, we only have 2.5D information and don't
/// know how thick surfaces are. We shall assume that the depth buffer
/// fragments are cuboids with a constant thickness defined by this
/// parameter.
pub thickness: f32,
/// The length of the contact shadow ray in world space.
pub length: f32,
}

impl Default for ContactShadows {
fn default() -> Self {
Self {
linear_steps: 16,
thickness: 0.1,
length: 0.3,
}
}
}

/// A version of [`ContactShadows`] for upload to the GPU.
#[derive(Clone, Copy, Component, ShaderType, Default)]
pub struct ContactShadowsUniform {
pub linear_steps: u32,
pub thickness: f32,
pub length: f32,
}

impl From<ContactShadows> for ContactShadowsUniform {
fn from(settings: ContactShadows) -> Self {
Self {
linear_steps: settings.linear_steps,
thickness: settings.thickness,
length: settings.length,
}
}
}

impl ExtractComponent for ContactShadows {
type QueryData = &'static ContactShadows;
type QueryFilter = ();
type Out = ContactShadows;

fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
Some(*settings)
}
}

/// A GPU buffer that stores the contact shadow settings for each view.
#[derive(Resource, Default)]
pub struct ContactShadowsBuffer(pub DynamicUniformBuffer<ContactShadowsUniform>);

impl Plugin for ContactShadowsPlugin {
fn build(&self, app: &mut App) {
app.register_type::<ContactShadows>()
.add_plugins(ExtractComponentPlugin::<ContactShadows>::default());

let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

render_app
.init_resource::<ContactShadowsBuffer>()
.add_systems(
Render,
prepare_contact_shadows_settings.in_set(RenderSystems::PrepareResources),
);
}
}

fn prepare_contact_shadows_settings(
mut commands: Commands,
views: Query<(Entity, Option<&ContactShadows>), With<ExtractedView>>,
mut contact_shadows_buffer: ResMut<ContactShadowsBuffer>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
contact_shadows_buffer.0.clear();
for (entity, settings) in &views {
let uniform = if let Some(settings) = settings {
ContactShadowsUniform::from(*settings)
} else {
ContactShadowsUniform {
linear_steps: 0,
..default()
}
};
let offset = contact_shadows_buffer.0.push(&uniform);
commands
.entity(entity)
.insert(ViewContactShadowsUniformOffset(offset));
}
contact_shadows_buffer
.0
.write_buffer(&render_device, &render_queue);
}

/// A component that stores the offset within the [`ContactShadowsBuffer`] for
/// each view.
#[derive(Component, Default, Deref, DerefMut)]
pub struct ViewContactShadowsUniformOffset(pub u32);
Loading