bevy/crates/bevy_light/src/directional_light.rs
atlv 537adcc3f7
bevy_light (#19991)
# Objective

- make lights usable without bevy_render

## Solution

- make a new crate for lights to live in

## Testing

- 3d_scene, lighting, volumetric_fog, ssr, transmission, pcss,
light_textures

Note: no breaking changes because of re-exports, except for light
textures, which were introduced this cycle so it doesn't matter anyways
2025-07-07 00:07:38 +00:00

232 lines
9.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use bevy_asset::Handle;
use bevy_camera::{
primitives::{CascadesFrusta, Frustum},
visibility::{self, CascadesVisibleEntities, ViewVisibility, Visibility, VisibilityClass},
Camera,
};
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_reflect::prelude::*;
use bevy_transform::components::Transform;
use super::{
cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades,
};
/// A Directional light.
///
/// Directional lights don't exist in reality but they are a good
/// approximation for light sources VERY far away, like the sun or
/// the moon.
///
/// The light shines along the forward direction of the entity's transform. With a default transform
/// this would be along the negative-Z axis.
///
/// Valid values for `illuminance` are:
///
/// | Illuminance (lux) | Surfaces illuminated by |
/// |-------------------|------------------------------------------------|
/// | 0.0001 | Moonless, overcast night sky (starlight) |
/// | 0.002 | Moonless clear night sky with airglow |
/// | 0.050.3 | Full moon on a clear night |
/// | 3.4 | Dark limit of civil twilight under a clear sky |
/// | 2050 | Public areas with dark surroundings |
/// | 50 | Family living room lights |
/// | 80 | Office building hallway/toilet lighting |
/// | 100 | Very dark overcast day |
/// | 150 | Train station platforms |
/// | 320500 | Office lighting |
/// | 400 | Sunrise or sunset on a clear day. |
/// | 1000 | Overcast day; typical TV studio lighting |
/// | 10,00025,000 | Full daylight (not direct sun) |
/// | 32,000100,000 | Direct sunlight |
///
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
///
/// ## Shadows
///
/// To enable shadows, set the `shadows_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).
///
/// To modify the cascade setup, such as the number of cascades or the maximum shadow distance,
/// change the [`CascadeShadowConfig`] component of the entity with the [`DirectionalLight`].
///
/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource.
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
#[require(
Cascades,
CascadesFrusta,
CascadeShadowConfig,
CascadesVisibleEntities,
Transform,
Visibility,
VisibilityClass
)]
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
pub struct DirectionalLight {
/// The color of the light.
///
/// By default, this is white.
pub color: Color,
/// Illuminance in lux (lumens per square meter), representing the amount of
/// light projected onto surfaces by this light source. Lux is used here
/// instead of lumens because a directional light illuminates all surfaces
/// more-or-less the same way (depending on the angle of incidence). Lumens
/// can only be specified for light sources which emit light from a specific
/// area.
pub illuminance: f32,
/// Whether this light casts shadows.
///
/// 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,
/// Whether soft shadows are enabled, and if so, the size of the light.
///
/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,
/// cause shadows to become blurrier (i.e. their penumbra increases in
/// radius) as they extend away from objects. The blurriness of the shadow
/// depends on the size of the light; larger lights result in larger
/// penumbras and therefore blurrier shadows.
///
/// Currently, soft shadows are rather noisy if not using the temporal mode.
/// If you enable soft shadows, consider choosing
/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing
/// (TAA) to smooth the noise out over time.
///
/// Note that soft shadows are significantly more expensive to render than
/// hard shadows.
///
/// [`ShadowFilteringMethod::Temporal`]: crate::ShadowFilteringMethod::Temporal
#[cfg(feature = "experimental_pbr_pcss")]
pub soft_shadow_size: Option<f32>,
/// Whether this directional light contributes diffuse lighting to meshes
/// with lightmaps.
///
/// Set this to false if your lightmap baking tool bakes the direct diffuse
/// light from this directional light into the lightmaps in order to avoid
/// counting the radiance from this light twice. Note that the specular
/// portion of the light is always considered, because Bevy currently has no
/// means to bake specular light.
///
/// By default, this is set to true.
pub affects_lightmapped_mesh_diffuse: bool,
/// A value that adjusts the tradeoff between self-shadowing artifacts and
/// proximity of shadows to their casters.
///
/// This value frequently must be tuned to the specific scene; this is
/// normal and a well-known part of the shadow mapping workflow. If set too
/// low, unsightly shadow patterns appear on objects not in shadow as
/// objects incorrectly cast shadows on themselves, known as *shadow acne*.
/// If set too high, shadows detach from the objects casting them and seem
/// to "fly" off the objects, known as *Peter Panning*.
pub shadow_depth_bias: f32,
/// 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.
pub shadow_normal_bias: f32,
}
impl Default for DirectionalLight {
fn default() -> Self {
DirectionalLight {
color: Color::WHITE,
illuminance: light_consts::lux::AMBIENT_DAYLIGHT,
shadows_enabled: false,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
affects_lightmapped_mesh_diffuse: true,
#[cfg(feature = "experimental_pbr_pcss")]
soft_shadow_size: None,
}
}
}
impl DirectionalLight {
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 1.8;
}
/// Add to a [`DirectionalLight`] to add a light texture effect.
/// A texture mask is applied to the light source to modulate its intensity,
/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.
#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Debug)]
#[require(DirectionalLight)]
pub struct DirectionalLightTexture {
/// The texture image. Only the R channel is read.
pub image: Handle<Image>,
/// Whether to tile the image infinitely, or use only a single tile centered at the light's translation
pub tiled: bool,
}
/// Controls the resolution of [`DirectionalLight`] shadow maps.
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_light::DirectionalLightShadowMap;
/// App::new()
/// .insert_resource(DirectionalLightShadowMap { size: 4096 });
/// ```
#[derive(Resource, Clone, Debug, Reflect)]
#[reflect(Resource, Debug, Default, Clone)]
pub struct DirectionalLightShadowMap {
// The width and height of each cascade.
///
/// Defaults to `2048`.
pub size: usize,
}
impl Default for DirectionalLightShadowMap {
fn default() -> Self {
Self { size: 2048 }
}
}
pub fn update_directional_light_frusta(
mut views: Query<
(
&Cascades,
&DirectionalLight,
&ViewVisibility,
&mut CascadesFrusta,
),
(
// Prevents this query from conflicting with camera queries.
Without<Camera>,
),
>,
) {
for (cascades, directional_light, visibility, mut frusta) in &mut views {
// 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() {
continue;
}
frusta.frusta = cascades
.cascades
.iter()
.map(|(view, cascades)| {
(
*view,
cascades
.iter()
.map(|c| Frustum::from_clip_from_world(&c.clip_from_world))
.collect::<Vec<_>>(),
)
})
.collect();
}
}