Path: blob/main/crates/bevy_light/src/directional_light.rs
6598 views
use bevy_asset::Handle;1use bevy_camera::{2primitives::{CascadesFrusta, Frustum},3visibility::{self, CascadesVisibleEntities, ViewVisibility, Visibility, VisibilityClass},4Camera,5};6use bevy_color::Color;7use bevy_ecs::prelude::*;8use bevy_image::Image;9use bevy_reflect::prelude::*;10use bevy_transform::components::Transform;11use tracing::warn;1213use super::{14cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades,15};1617/// A Directional light.18///19/// Directional lights don't exist in reality but they are a good20/// approximation for light sources VERY far away, like the sun or21/// the moon.22///23/// The light shines along the forward direction of the entity's transform. With a default transform24/// this would be along the negative-Z axis.25///26/// Valid values for `illuminance` are:27///28/// | Illuminance (lux) | Surfaces illuminated by |29/// |-------------------|------------------------------------------------|30/// | 0.0001 | Moonless, overcast night sky (starlight) |31/// | 0.002 | Moonless clear night sky with airglow |32/// | 0.05–0.3 | Full moon on a clear night |33/// | 3.4 | Dark limit of civil twilight under a clear sky |34/// | 20–50 | Public areas with dark surroundings |35/// | 50 | Family living room lights |36/// | 80 | Office building hallway/toilet lighting |37/// | 100 | Very dark overcast day |38/// | 150 | Train station platforms |39/// | 320–500 | Office lighting |40/// | 400 | Sunrise or sunset on a clear day. |41/// | 1000 | Overcast day; typical TV studio lighting |42/// | 10,000–25,000 | Full daylight (not direct sun) |43/// | 32,000–100,000 | Direct sunlight |44///45/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)46///47/// ## Shadows48///49/// To enable shadows, set the `shadows_enabled` property to `true`.50///51/// 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).52///53/// To modify the cascade setup, such as the number of cascades or the maximum shadow distance,54/// change the [`CascadeShadowConfig`] component of the entity with the [`DirectionalLight`].55///56/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource.57#[derive(Component, Debug, Clone, Reflect)]58#[reflect(Component, Default, Debug, Clone)]59#[require(60Cascades,61CascadesFrusta,62CascadeShadowConfig,63CascadesVisibleEntities,64Transform,65Visibility,66VisibilityClass67)]68#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]69pub struct DirectionalLight {70/// The color of the light.71///72/// By default, this is white.73pub color: Color,7475/// Illuminance in lux (lumens per square meter), representing the amount of76/// light projected onto surfaces by this light source. Lux is used here77/// instead of lumens because a directional light illuminates all surfaces78/// more-or-less the same way (depending on the angle of incidence). Lumens79/// can only be specified for light sources which emit light from a specific80/// area.81pub illuminance: f32,8283/// Whether this light casts shadows.84///85/// Note that shadows are rather expensive and become more so with every86/// light that casts them. In general, it's best to aggressively limit the87/// number of lights with shadows enabled to one or two at most.88pub shadows_enabled: bool,8990/// Whether soft shadows are enabled, and if so, the size of the light.91///92/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,93/// cause shadows to become blurrier (i.e. their penumbra increases in94/// radius) as they extend away from objects. The blurriness of the shadow95/// depends on the size of the light; larger lights result in larger96/// penumbras and therefore blurrier shadows.97///98/// Currently, soft shadows are rather noisy if not using the temporal mode.99/// If you enable soft shadows, consider choosing100/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing101/// (TAA) to smooth the noise out over time.102///103/// Note that soft shadows are significantly more expensive to render than104/// hard shadows.105///106/// [`ShadowFilteringMethod::Temporal`]: crate::ShadowFilteringMethod::Temporal107#[cfg(feature = "experimental_pbr_pcss")]108pub soft_shadow_size: Option<f32>,109110/// Whether this directional light contributes diffuse lighting to meshes111/// with lightmaps.112///113/// Set this to false if your lightmap baking tool bakes the direct diffuse114/// light from this directional light into the lightmaps in order to avoid115/// counting the radiance from this light twice. Note that the specular116/// portion of the light is always considered, because Bevy currently has no117/// means to bake specular light.118///119/// By default, this is set to true.120pub affects_lightmapped_mesh_diffuse: bool,121122/// A value that adjusts the tradeoff between self-shadowing artifacts and123/// proximity of shadows to their casters.124///125/// This value frequently must be tuned to the specific scene; this is126/// normal and a well-known part of the shadow mapping workflow. If set too127/// low, unsightly shadow patterns appear on objects not in shadow as128/// objects incorrectly cast shadows on themselves, known as *shadow acne*.129/// If set too high, shadows detach from the objects casting them and seem130/// to "fly" off the objects, known as *Peter Panning*.131pub shadow_depth_bias: f32,132133/// A bias applied along the direction of the fragment's surface normal. It134/// is scaled to the shadow map's texel size so that it is automatically135/// adjusted to the orthographic projection.136pub shadow_normal_bias: f32,137}138139impl Default for DirectionalLight {140fn default() -> Self {141DirectionalLight {142color: Color::WHITE,143illuminance: light_consts::lux::AMBIENT_DAYLIGHT,144shadows_enabled: false,145shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,146shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,147affects_lightmapped_mesh_diffuse: true,148#[cfg(feature = "experimental_pbr_pcss")]149soft_shadow_size: None,150}151}152}153154impl DirectionalLight {155pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;156pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 1.8;157}158159/// Add to a [`DirectionalLight`] to add a light texture effect.160/// A texture mask is applied to the light source to modulate its intensity,161/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.162#[derive(Clone, Component, Debug, Reflect)]163#[reflect(Component, Debug)]164#[require(DirectionalLight)]165pub struct DirectionalLightTexture {166/// The texture image. Only the R channel is read.167pub image: Handle<Image>,168/// Whether to tile the image infinitely, or use only a single tile centered at the light's translation169pub tiled: bool,170}171172/// Controls the resolution of [`DirectionalLight`] and [`SpotLight`](crate::SpotLight) shadow maps.173///174/// ```175/// # use bevy_app::prelude::*;176/// # use bevy_light::DirectionalLightShadowMap;177/// App::new()178/// .insert_resource(DirectionalLightShadowMap { size: 4096 });179/// ```180#[derive(Resource, Clone, Debug, Reflect)]181#[reflect(Resource, Debug, Default, Clone)]182pub struct DirectionalLightShadowMap {183// The width and height of each cascade.184///185/// Must be a power of two to avoid unstable cascade positioning.186///187/// Defaults to `2048`.188pub size: usize,189}190191impl Default for DirectionalLightShadowMap {192fn default() -> Self {193Self { size: 2048 }194}195}196197pub fn validate_shadow_map_size(mut shadow_map: ResMut<DirectionalLightShadowMap>) {198if shadow_map.is_changed() && !shadow_map.size.is_power_of_two() {199let new_size = shadow_map.size.next_power_of_two();200warn!("Non-power-of-two DirectionalLightShadowMap sizes are not supported, correcting {} to {new_size}", shadow_map.size);201shadow_map.size = new_size;202}203}204205pub fn update_directional_light_frusta(206mut views: Query<207(208&Cascades,209&DirectionalLight,210&ViewVisibility,211&mut CascadesFrusta,212),213(214// Prevents this query from conflicting with camera queries.215Without<Camera>,216),217>,218) {219for (cascades, directional_light, visibility, mut frusta) in &mut views {220// The frustum is used for culling meshes to the light for shadow mapping221// so if shadow mapping is disabled for this light, then the frustum is222// not needed.223if !directional_light.shadows_enabled || !visibility.get() {224continue;225}226227frusta.frusta = cascades228.cascades229.iter()230.map(|(view, cascades)| {231(232*view,233cascades234.iter()235.map(|c| Frustum::from_clip_from_world(&c.clip_from_world))236.collect::<Vec<_>>(),237)238})239.collect();240}241}242243/// Add to a [`DirectionalLight`] to control rendering of the visible solar disk in the sky.244/// Affects only the disk’s appearance, not the light’s illuminance or shadows.245/// Requires a `bevy::pbr::Atmosphere` component on a [`Camera3d`](bevy_camera::Camera3d) to have any effect.246///247/// By default, the atmosphere is rendered with [`SunDisk::EARTH`], which approximates the248/// apparent size and brightness of the Sun as seen from Earth. You can also disable the sun249/// disk entirely with [`SunDisk::OFF`].250#[derive(Component, Clone)]251#[require(DirectionalLight)]252pub struct SunDisk {253/// The angular size (diameter) of the sun disk in radians, as observed from the scene.254pub angular_size: f32,255/// Multiplier for the brightness of the sun disk.256///257/// `0.0` disables the disk entirely (atmospheric scattering still occurs),258/// `1.0` is the default physical intensity, and values `>1.0` overexpose it.259pub intensity: f32,260}261262impl SunDisk {263/// Earth-like parameters for the sun disk.264///265/// Uses the mean apparent size (~32 arcminutes) of the Sun at 1 AU distance266/// with default intensity.267pub const EARTH: SunDisk = SunDisk {268angular_size: 0.00930842,269intensity: 1.0,270};271272/// No visible sun disk.273///274/// Keeps scattering and directional light illumination, but hides the disk itself.275pub const OFF: SunDisk = SunDisk {276angular_size: 0.0,277intensity: 0.0,278};279}280281impl Default for SunDisk {282fn default() -> Self {283Self::EARTH284}285}286287impl Default for &SunDisk {288fn default() -> Self {289&SunDisk::EARTH290}291}292293294