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