Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_light/src/directional_light.rs
6598 views
1
use bevy_asset::Handle;
2
use bevy_camera::{
3
primitives::{CascadesFrusta, Frustum},
4
visibility::{self, CascadesVisibleEntities, ViewVisibility, Visibility, VisibilityClass},
5
Camera,
6
};
7
use bevy_color::Color;
8
use bevy_ecs::prelude::*;
9
use bevy_image::Image;
10
use bevy_reflect::prelude::*;
11
use bevy_transform::components::Transform;
12
use tracing::warn;
13
14
use super::{
15
cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades,
16
};
17
18
/// A Directional light.
19
///
20
/// Directional lights don't exist in reality but they are a good
21
/// approximation for light sources VERY far away, like the sun or
22
/// the moon.
23
///
24
/// The light shines along the forward direction of the entity's transform. With a default transform
25
/// 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
/// ## Shadows
49
///
50
/// To enable shadows, set the `shadows_enabled` property to `true`.
51
///
52
/// 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).
53
///
54
/// To modify the cascade setup, such as the number of cascades or the maximum shadow distance,
55
/// change the [`CascadeShadowConfig`] component of the entity with the [`DirectionalLight`].
56
///
57
/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource.
58
#[derive(Component, Debug, Clone, Reflect)]
59
#[reflect(Component, Default, Debug, Clone)]
60
#[require(
61
Cascades,
62
CascadesFrusta,
63
CascadeShadowConfig,
64
CascadesVisibleEntities,
65
Transform,
66
Visibility,
67
VisibilityClass
68
)]
69
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
70
pub struct DirectionalLight {
71
/// The color of the light.
72
///
73
/// By default, this is white.
74
pub color: Color,
75
76
/// Illuminance in lux (lumens per square meter), representing the amount of
77
/// light projected onto surfaces by this light source. Lux is used here
78
/// instead of lumens because a directional light illuminates all surfaces
79
/// more-or-less the same way (depending on the angle of incidence). Lumens
80
/// can only be specified for light sources which emit light from a specific
81
/// area.
82
pub illuminance: f32,
83
84
/// Whether this light casts shadows.
85
///
86
/// Note that shadows are rather expensive and become more so with every
87
/// light that casts them. In general, it's best to aggressively limit the
88
/// number of lights with shadows enabled to one or two at most.
89
pub shadows_enabled: bool,
90
91
/// Whether soft shadows are enabled, and if so, the size of the light.
92
///
93
/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,
94
/// cause shadows to become blurrier (i.e. their penumbra increases in
95
/// radius) as they extend away from objects. The blurriness of the shadow
96
/// depends on the size of the light; larger lights result in larger
97
/// penumbras and therefore blurrier shadows.
98
///
99
/// Currently, soft shadows are rather noisy if not using the temporal mode.
100
/// If you enable soft shadows, consider choosing
101
/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing
102
/// (TAA) to smooth the noise out over time.
103
///
104
/// Note that soft shadows are significantly more expensive to render than
105
/// hard shadows.
106
///
107
/// [`ShadowFilteringMethod::Temporal`]: crate::ShadowFilteringMethod::Temporal
108
#[cfg(feature = "experimental_pbr_pcss")]
109
pub soft_shadow_size: Option<f32>,
110
111
/// Whether this directional light contributes diffuse lighting to meshes
112
/// with lightmaps.
113
///
114
/// Set this to false if your lightmap baking tool bakes the direct diffuse
115
/// light from this directional light into the lightmaps in order to avoid
116
/// counting the radiance from this light twice. Note that the specular
117
/// portion of the light is always considered, because Bevy currently has no
118
/// means to bake specular light.
119
///
120
/// By default, this is set to true.
121
pub affects_lightmapped_mesh_diffuse: bool,
122
123
/// A value that adjusts the tradeoff between self-shadowing artifacts and
124
/// proximity of shadows to their casters.
125
///
126
/// This value frequently must be tuned to the specific scene; this is
127
/// normal and a well-known part of the shadow mapping workflow. If set too
128
/// low, unsightly shadow patterns appear on objects not in shadow as
129
/// objects incorrectly cast shadows on themselves, known as *shadow acne*.
130
/// If set too high, shadows detach from the objects casting them and seem
131
/// to "fly" off the objects, known as *Peter Panning*.
132
pub shadow_depth_bias: f32,
133
134
/// A bias applied along the direction of the fragment's surface normal. It
135
/// is scaled to the shadow map's texel size so that it is automatically
136
/// adjusted to the orthographic projection.
137
pub shadow_normal_bias: f32,
138
}
139
140
impl Default for DirectionalLight {
141
fn default() -> Self {
142
DirectionalLight {
143
color: Color::WHITE,
144
illuminance: light_consts::lux::AMBIENT_DAYLIGHT,
145
shadows_enabled: false,
146
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
147
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
148
affects_lightmapped_mesh_diffuse: true,
149
#[cfg(feature = "experimental_pbr_pcss")]
150
soft_shadow_size: None,
151
}
152
}
153
}
154
155
impl DirectionalLight {
156
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
157
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 1.8;
158
}
159
160
/// Add to a [`DirectionalLight`] to add a light texture effect.
161
/// A texture mask is applied to the light source to modulate its intensity,
162
/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.
163
#[derive(Clone, Component, Debug, Reflect)]
164
#[reflect(Component, Debug)]
165
#[require(DirectionalLight)]
166
pub struct DirectionalLightTexture {
167
/// The texture image. Only the R channel is read.
168
pub image: Handle<Image>,
169
/// Whether to tile the image infinitely, or use only a single tile centered at the light's translation
170
pub tiled: bool,
171
}
172
173
/// Controls the resolution of [`DirectionalLight`] and [`SpotLight`](crate::SpotLight) shadow maps.
174
///
175
/// ```
176
/// # use bevy_app::prelude::*;
177
/// # use bevy_light::DirectionalLightShadowMap;
178
/// App::new()
179
/// .insert_resource(DirectionalLightShadowMap { size: 4096 });
180
/// ```
181
#[derive(Resource, Clone, Debug, Reflect)]
182
#[reflect(Resource, Debug, Default, Clone)]
183
pub struct DirectionalLightShadowMap {
184
// The width and height of each cascade.
185
///
186
/// Must be a power of two to avoid unstable cascade positioning.
187
///
188
/// Defaults to `2048`.
189
pub size: usize,
190
}
191
192
impl Default for DirectionalLightShadowMap {
193
fn default() -> Self {
194
Self { size: 2048 }
195
}
196
}
197
198
pub fn validate_shadow_map_size(mut shadow_map: ResMut<DirectionalLightShadowMap>) {
199
if shadow_map.is_changed() && !shadow_map.size.is_power_of_two() {
200
let new_size = shadow_map.size.next_power_of_two();
201
warn!("Non-power-of-two DirectionalLightShadowMap sizes are not supported, correcting {} to {new_size}", shadow_map.size);
202
shadow_map.size = new_size;
203
}
204
}
205
206
pub fn update_directional_light_frusta(
207
mut views: Query<
208
(
209
&Cascades,
210
&DirectionalLight,
211
&ViewVisibility,
212
&mut CascadesFrusta,
213
),
214
(
215
// Prevents this query from conflicting with camera queries.
216
Without<Camera>,
217
),
218
>,
219
) {
220
for (cascades, directional_light, visibility, mut frusta) in &mut views {
221
// The frustum is used for culling meshes to the light for shadow mapping
222
// so if shadow mapping is disabled for this light, then the frustum is
223
// not needed.
224
if !directional_light.shadows_enabled || !visibility.get() {
225
continue;
226
}
227
228
frusta.frusta = cascades
229
.cascades
230
.iter()
231
.map(|(view, cascades)| {
232
(
233
*view,
234
cascades
235
.iter()
236
.map(|c| Frustum::from_clip_from_world(&c.clip_from_world))
237
.collect::<Vec<_>>(),
238
)
239
})
240
.collect();
241
}
242
}
243
244
/// Add to a [`DirectionalLight`] to control rendering of the visible solar disk in the sky.
245
/// Affects only the disk’s appearance, not the light’s illuminance or shadows.
246
/// Requires a `bevy::pbr::Atmosphere` component on a [`Camera3d`](bevy_camera::Camera3d) to have any effect.
247
///
248
/// By default, the atmosphere is rendered with [`SunDisk::EARTH`], which approximates the
249
/// apparent size and brightness of the Sun as seen from Earth. You can also disable the sun
250
/// disk entirely with [`SunDisk::OFF`].
251
#[derive(Component, Clone)]
252
#[require(DirectionalLight)]
253
pub struct SunDisk {
254
/// The angular size (diameter) of the sun disk in radians, as observed from the scene.
255
pub angular_size: f32,
256
/// Multiplier for the brightness of the sun disk.
257
///
258
/// `0.0` disables the disk entirely (atmospheric scattering still occurs),
259
/// `1.0` is the default physical intensity, and values `>1.0` overexpose it.
260
pub intensity: f32,
261
}
262
263
impl SunDisk {
264
/// Earth-like parameters for the sun disk.
265
///
266
/// Uses the mean apparent size (~32 arcminutes) of the Sun at 1 AU distance
267
/// with default intensity.
268
pub const EARTH: SunDisk = SunDisk {
269
angular_size: 0.00930842,
270
intensity: 1.0,
271
};
272
273
/// No visible sun disk.
274
///
275
/// Keeps scattering and directional light illumination, but hides the disk itself.
276
pub const OFF: SunDisk = SunDisk {
277
angular_size: 0.0,
278
intensity: 0.0,
279
};
280
}
281
282
impl Default for SunDisk {
283
fn default() -> Self {
284
Self::EARTH
285
}
286
}
287
288
impl Default for &SunDisk {
289
fn default() -> Self {
290
&SunDisk::EARTH
291
}
292
}
293
294