Path: blob/main/crates/bevy_light/src/directional_light.rs
9371 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_math::primitives::ViewFrustum;10use bevy_reflect::prelude::*;11use bevy_transform::components::Transform;12use tracing::warn;1314use super::{15cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades,16};1718/// A Directional light.19///20/// Directional lights don't exist in reality but they are a good21/// approximation for light sources VERY far away, like the sun or22/// the moon.23///24/// The light shines along the forward direction of the entity's transform. With a default transform25/// this would be along the negative-Z axis.26///27/// Valid values for `illuminance` are:28///29/// | Illuminance (lux) | Surfaces illuminated by |30/// |-------------------|------------------------------------------------|31/// | 0.0001 | Moonless, overcast night sky (starlight) |32/// | 0.002 | Moonless clear night sky with airglow |33/// | 0.05–0.3 | Full moon on a clear night |34/// | 3.4 | Dark limit of civil twilight under a clear sky |35/// | 20–50 | Public areas with dark surroundings |36/// | 50 | Family living room lights |37/// | 80 | Office building hallway/toilet lighting |38/// | 100 | Very dark overcast day |39/// | 150 | Train station platforms |40/// | 320–500 | Office lighting |41/// | 400 | Sunrise or sunset on a clear day. |42/// | 1000 | Overcast day; typical TV studio lighting |43/// | 10,000–25,000 | Full daylight (not direct sun) |44/// | 32,000–100,000 | Direct sunlight |45///46/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)47///48/// Some of these are provided as constants in [`light_consts::lux`].49///50/// ## Shadows51///52/// To enable shadows, set the `shadow_maps_enabled` property to `true`.53///54/// 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).55///56/// To modify the cascade setup, such as the number of cascades or the maximum shadow distance,57/// change the [`CascadeShadowConfig`] component of the entity with the [`DirectionalLight`].58///59/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource.60#[derive(Component, Debug, Clone, Copy, Reflect)]61#[reflect(Component, Default, Debug, Clone)]62#[require(63Cascades,64CascadesFrusta,65CascadeShadowConfig,66CascadesVisibleEntities,67Transform,68Visibility,69VisibilityClass70)]71#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]72pub struct DirectionalLight {73/// The color of the light.74///75/// By default, this is white.76pub color: Color,7778/// Illuminance in lux (lumens per square meter), representing the amount of79/// light projected onto surfaces by this light source. Lux is used here80/// instead of lumens because a directional light illuminates all surfaces81/// more-or-less the same way (depending on the angle of incidence). Lumens82/// can only be specified for light sources which emit light from a specific83/// area.84/// The default is [`light_consts::lux::AMBIENT_DAYLIGHT`] = 10,000.85pub illuminance: f32,8687/// Whether this light casts shadows.88///89/// Note that shadows are rather expensive and become more so with every90/// light that casts them. In general, it's best to aggressively limit the91/// number of lights with shadows enabled to one or two at most.92pub shadow_maps_enabled: bool,9394/// Whether this light casts contact shadows.95pub contact_shadows_enabled: bool,9697/// Whether soft shadows are enabled, and if so, the size of the light.98///99/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,100/// cause shadows to become blurrier (i.e. their penumbra increases in101/// radius) as they extend away from objects. The blurriness of the shadow102/// depends on the size of the light; larger lights result in larger103/// penumbras and therefore blurrier shadows.104///105/// Currently, soft shadows are rather noisy if not using the temporal mode.106/// If you enable soft shadows, consider choosing107/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing108/// (TAA) to smooth the noise out over time.109///110/// Note that soft shadows are significantly more expensive to render than111/// hard shadows.112///113/// [`ShadowFilteringMethod::Temporal`]: crate::ShadowFilteringMethod::Temporal114#[cfg(feature = "experimental_pbr_pcss")]115pub soft_shadow_size: Option<f32>,116117/// Whether this directional light contributes diffuse lighting to meshes118/// with lightmaps.119///120/// Set this to false if your lightmap baking tool bakes the direct diffuse121/// light from this directional light into the lightmaps in order to avoid122/// counting the radiance from this light twice. Note that the specular123/// portion of the light is always considered, because Bevy currently has no124/// means to bake specular light.125///126/// By default, this is set to true.127pub affects_lightmapped_mesh_diffuse: bool,128129/// A value that adjusts the tradeoff between self-shadowing artifacts and130/// proximity of shadows to their casters.131///132/// This value frequently must be tuned to the specific scene; this is133/// normal and a well-known part of the shadow mapping workflow. If set too134/// low, unsightly shadow patterns appear on objects not in shadow as135/// objects incorrectly cast shadows on themselves, known as *shadow acne*.136/// If set too high, shadows detach from the objects casting them and seem137/// to "fly" off the objects, known as *Peter Panning*.138pub shadow_depth_bias: f32,139140/// A bias applied along the direction of the fragment's surface normal. It141/// is scaled to the shadow map's texel size so that it is automatically142/// adjusted to the orthographic projection.143pub shadow_normal_bias: f32,144}145146impl Default for DirectionalLight {147fn default() -> Self {148DirectionalLight {149color: Color::WHITE,150illuminance: light_consts::lux::AMBIENT_DAYLIGHT,151shadow_maps_enabled: false,152contact_shadows_enabled: false,153shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,154shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,155affects_lightmapped_mesh_diffuse: true,156#[cfg(feature = "experimental_pbr_pcss")]157soft_shadow_size: None,158}159}160}161162impl DirectionalLight {163/// The default value of [`DirectionalLight::shadow_depth_bias`].164pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;165/// The default value of [`DirectionalLight::shadow_normal_bias`].166pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 1.8;167}168169/// Add to a [`DirectionalLight`] to add a light texture effect.170/// A texture mask is applied to the light source to modulate its intensity,171/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.172#[derive(Clone, Component, Debug, Reflect)]173#[reflect(Component, Debug)]174#[require(DirectionalLight)]175pub struct DirectionalLightTexture {176/// The texture image. Only the R channel is read.177pub image: Handle<Image>,178/// Whether to tile the image infinitely, or use only a single tile centered at the light's translation179pub tiled: bool,180}181182/// Controls the resolution of [`DirectionalLight`] and [`SpotLight`](crate::SpotLight) shadow maps.183///184/// ```185/// # use bevy_app::prelude::*;186/// # use bevy_light::DirectionalLightShadowMap;187/// App::new()188/// .insert_resource(DirectionalLightShadowMap { size: 4096 });189/// ```190#[derive(Resource, Clone, Debug, Reflect)]191#[reflect(Resource, Debug, Default, Clone)]192pub struct DirectionalLightShadowMap {193// The width and height of each cascade.194///195/// Must be a power of two to avoid unstable cascade positioning.196///197/// Defaults to `2048`.198pub size: usize,199}200201impl Default for DirectionalLightShadowMap {202fn default() -> Self {203Self { size: 2048 }204}205}206207pub fn validate_shadow_map_size(mut shadow_map: ResMut<DirectionalLightShadowMap>) {208if shadow_map.is_changed() && !shadow_map.size.is_power_of_two() {209let new_size = shadow_map.size.next_power_of_two();210warn!("Non-power-of-two DirectionalLightShadowMap sizes are not supported, correcting {} to {new_size}", shadow_map.size);211shadow_map.size = new_size;212}213}214215/// Updates the frusta for all visible shadow mapped [`DirectionalLight`]s.216pub fn update_directional_light_frusta(217mut views: Query<218(219&Cascades,220&DirectionalLight,221&ViewVisibility,222&mut CascadesFrusta,223),224(225// Prevents this query from conflicting with camera queries.226Without<Camera>,227),228>,229) {230for (cascades, directional_light, visibility, mut frusta) in &mut views {231// The frustum is used for culling meshes to the light for shadow mapping232// so if shadow mapping is disabled for this light, then the frustum is233// not needed.234if !directional_light.shadow_maps_enabled || !visibility.get() {235continue;236}237238frusta.frusta = cascades239.cascades240.iter()241.map(|(view, cascades)| {242(243*view,244cascades245.iter()246.map(|c| Frustum(ViewFrustum::from_clip_from_world(&c.clip_from_world)))247.collect::<Vec<_>>(),248)249})250.collect();251}252}253254/// Add to a [`DirectionalLight`] to control rendering of the visible solar disk in the sky.255/// Affects only the disk’s appearance, not the light’s illuminance or shadows.256/// Requires a `bevy::pbr::Atmosphere` component on a [`Camera3d`](bevy_camera::Camera3d) to have any effect.257///258/// By default, the atmosphere is rendered with [`SunDisk::EARTH`], which approximates the259/// apparent size and brightness of the Sun as seen from Earth. You can also disable the sun260/// disk entirely with [`SunDisk::OFF`].261///262/// In order to cause the sun to "glow" and light up the surrounding sky, enable bloom263/// in your post-processing pipeline by adding a `Bloom` component to your camera.264#[derive(Component, Clone)]265#[require(DirectionalLight)]266pub struct SunDisk {267/// The angular size (diameter) of the sun disk in radians, as observed from the scene.268pub angular_size: f32,269/// Multiplier for the brightness of the sun disk.270///271/// `0.0` disables the disk entirely (atmospheric scattering still occurs),272/// `1.0` is the default physical intensity, and values `>1.0` overexpose it.273pub intensity: f32,274}275276impl SunDisk {277/// Earth-like parameters for the sun disk.278///279/// Uses the mean apparent size (~32 arcminutes) of the Sun at 1 AU distance280/// with default intensity.281pub const EARTH: SunDisk = SunDisk {282angular_size: 0.00930842,283intensity: 1.0,284};285286/// No visible sun disk.287///288/// Keeps scattering and directional light illumination, but hides the disk itself.289pub const OFF: SunDisk = SunDisk {290angular_size: 0.0,291intensity: 0.0,292};293}294295impl Default for SunDisk {296fn default() -> Self {297Self::EARTH298}299}300301impl Default for &SunDisk {302fn default() -> Self {303&SunDisk::EARTH304}305}306307308