1#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
2
3use bevy_app::{App, Plugin, PostUpdate};
4use bevy_camera::{
5 primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere},
6 visibility::{
7 CascadesVisibleEntities, CubemapVisibleEntities, InheritedVisibility, NoFrustumCulling,
8 RenderLayers, ViewVisibility, VisibilityRange, VisibilitySystems, VisibleEntityRanges,
9 VisibleMeshEntities,
10 },
11 CameraUpdateSystems,
12};
13use bevy_ecs::{entity::EntityHashSet, prelude::*};
14use bevy_math::Vec3A;
15use bevy_mesh::Mesh3d;
16use bevy_reflect::prelude::*;
17use bevy_transform::{components::GlobalTransform, TransformSystems};
18use bevy_utils::Parallel;
19use core::ops::DerefMut;
20
21pub mod cluster;
22pub use cluster::ClusteredDecal;
23use cluster::{
24 add_clusters, assign::assign_objects_to_clusters, GlobalVisibleClusterableObjects,
25 VisibleClusterableObjects,
26};
27mod ambient_light;
28pub use ambient_light::{AmbientLight, GlobalAmbientLight};
29use bevy_camera::visibility::SetViewVisibility;
30
31mod probe;
32pub use probe::{
33 AtmosphereEnvironmentMapLight, EnvironmentMapLight, GeneratedEnvironmentMapLight,
34 IrradianceVolume, LightProbe,
35};
36mod volumetric;
37pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight};
38pub mod cascade;
39use cascade::{build_directional_light_cascades, clear_directional_light_cascades};
40pub use cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
41mod point_light;
42pub use point_light::{
43 update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture,
44};
45mod spot_light;
46pub use spot_light::{
47 orthonormalize, spot_light_clip_from_view, spot_light_world_from_view,
48 update_spot_light_frusta, SpotLight, SpotLightTexture,
49};
50mod directional_light;
51pub use directional_light::{
52 update_directional_light_frusta, DirectionalLight, DirectionalLightShadowMap,
53 DirectionalLightTexture, SunDisk,
54};
55
56pub mod prelude {
60 #[doc(hidden)]
61 pub use crate::{
62 light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight,
63 GeneratedEnvironmentMapLight, GlobalAmbientLight, LightProbe, PointLight, SpotLight,
64 };
65}
66
67use crate::directional_light::validate_shadow_map_size;
68
69pub mod light_consts {
71 pub mod lumens {
83 pub const LUMENS_PER_LED_WATTS: f32 = 90.0;
84 pub const LUMENS_PER_INCANDESCENT_WATTS: f32 = 13.8;
85 pub const LUMENS_PER_HALOGEN_WATTS: f32 = 19.8;
86 pub const VERY_LARGE_CINEMA_LIGHT: f32 = 1_000_000.0;
90 }
91
92 pub mod lux {
103 pub const MOONLESS_NIGHT: f32 = 0.0001;
105 pub const FULL_MOON_NIGHT: f32 = 0.05;
107 pub const CIVIL_TWILIGHT: f32 = 3.4;
109 pub const LIVING_ROOM: f32 = 50.;
111 pub const HALLWAY: f32 = 80.;
113 pub const DARK_OVERCAST_DAY: f32 = 100.;
115 pub const OFFICE: f32 = 320.;
117 pub const CLEAR_SUNRISE: f32 = 400.;
119 pub const OVERCAST_DAY: f32 = 1000.;
121 pub const AMBIENT_DAYLIGHT: f32 = 10_000.;
123 pub const FULL_DAYLIGHT: f32 = 20_000.;
125 pub const DIRECT_SUNLIGHT: f32 = 100_000.;
127 pub const RAW_SUNLIGHT: f32 = 130_000.;
129 }
130}
131
132#[derive(Default)]
133pub struct LightPlugin;
134
135impl Plugin for LightPlugin {
136 fn build(&self, app: &mut App) {
137 app.init_resource::<GlobalVisibleClusterableObjects>()
138 .init_resource::<GlobalAmbientLight>()
139 .init_resource::<DirectionalLightShadowMap>()
140 .init_resource::<PointLightShadowMap>()
141 .configure_sets(
142 PostUpdate,
143 SimulationLightSystems::UpdateDirectionalLightCascades
144 .ambiguous_with(SimulationLightSystems::UpdateDirectionalLightCascades),
145 )
146 .configure_sets(
147 PostUpdate,
148 SimulationLightSystems::CheckLightVisibility
149 .ambiguous_with(SimulationLightSystems::CheckLightVisibility),
150 )
151 .add_systems(
152 PostUpdate,
153 (
154 validate_shadow_map_size.before(build_directional_light_cascades),
155 add_clusters
156 .in_set(SimulationLightSystems::AddClusters)
157 .after(CameraUpdateSystems),
158 assign_objects_to_clusters
159 .in_set(SimulationLightSystems::AssignLightsToClusters)
160 .after(TransformSystems::Propagate)
161 .after(VisibilitySystems::CheckVisibility)
162 .after(CameraUpdateSystems),
163 clear_directional_light_cascades
164 .in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
165 .after(TransformSystems::Propagate)
166 .after(CameraUpdateSystems),
167 update_directional_light_frusta
168 .in_set(SimulationLightSystems::UpdateLightFrusta)
169 .after(VisibilitySystems::CheckVisibility)
171 .after(TransformSystems::Propagate)
172 .after(SimulationLightSystems::UpdateDirectionalLightCascades)
173 .ambiguous_with(update_spot_light_frusta),
177 update_point_light_frusta
178 .in_set(SimulationLightSystems::UpdateLightFrusta)
179 .after(TransformSystems::Propagate)
180 .after(SimulationLightSystems::AssignLightsToClusters),
181 update_spot_light_frusta
182 .in_set(SimulationLightSystems::UpdateLightFrusta)
183 .after(TransformSystems::Propagate)
184 .after(SimulationLightSystems::AssignLightsToClusters),
185 (
186 check_dir_light_mesh_visibility,
187 check_point_light_mesh_visibility,
188 )
189 .in_set(SimulationLightSystems::CheckLightVisibility)
190 .after(VisibilitySystems::CalculateBounds)
191 .after(TransformSystems::Propagate)
192 .after(SimulationLightSystems::UpdateLightFrusta)
193 .after(VisibilitySystems::CheckVisibility)
198 .before(VisibilitySystems::MarkNewlyHiddenEntitiesInvisible),
199 build_directional_light_cascades
200 .in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
201 .after(clear_directional_light_cascades),
202 ),
203 );
204 }
205}
206
207pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>;
210
211#[derive(Debug, Component, Reflect, Default, Clone, PartialEq)]
213#[reflect(Component, Default, Debug, Clone, PartialEq)]
214pub struct NotShadowCaster;
215#[derive(Debug, Component, Reflect, Default)]
221#[reflect(Component, Default, Debug)]
222pub struct NotShadowReceiver;
223#[derive(Debug, Component, Reflect, Default)]
231#[reflect(Component, Default, Debug)]
232pub struct TransmittedShadowReceiver;
233
234#[derive(Debug, Component, Reflect, Clone, Copy, PartialEq, Eq, Default)]
240#[reflect(Component, Default, Debug, PartialEq, Clone)]
241pub enum ShadowFilteringMethod {
242 Hardware2x2,
246 #[default]
256 Gaussian,
257 Temporal,
268}
269
270#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
272pub enum SimulationLightSystems {
273 AddClusters,
274 AssignLightsToClusters,
275 UpdateDirectionalLightCascades,
279 UpdateLightFrusta,
280 CheckLightVisibility,
284}
285
286fn shrink_entities(visible_entities: &mut Vec<Entity>) {
287 let capacity = visible_entities.capacity();
289 let reserved = capacity
290 .checked_div(visible_entities.len())
291 .map_or(0, |reserve| {
292 if reserve > 2 {
293 capacity / (reserve / 2)
294 } else {
295 capacity
296 }
297 });
298
299 visible_entities.shrink_to(reserved);
300}
301
302pub fn check_dir_light_mesh_visibility(
303 mut commands: Commands,
304 mut directional_lights: Query<
305 (
306 &DirectionalLight,
307 &CascadesFrusta,
308 &mut CascadesVisibleEntities,
309 Option<&RenderLayers>,
310 &ViewVisibility,
311 ),
312 Without<SpotLight>,
313 >,
314 visible_entity_query: Query<
315 (
316 Entity,
317 &InheritedVisibility,
318 Option<&RenderLayers>,
319 Option<&Aabb>,
320 Option<&GlobalTransform>,
321 Has<VisibilityRange>,
322 Has<NoFrustumCulling>,
323 ),
324 (
325 Without<NotShadowCaster>,
326 Without<DirectionalLight>,
327 With<Mesh3d>,
328 ),
329 >,
330 visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
331 mut defer_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
332 mut view_visible_entities_queue: Local<Parallel<Vec<Vec<Entity>>>>,
333) {
334 let visible_entity_ranges = visible_entity_ranges.as_deref();
335
336 for (directional_light, frusta, mut visible_entities, maybe_view_mask, light_view_visibility) in
337 &mut directional_lights
338 {
339 let mut views_to_remove = Vec::new();
340 for (view, cascade_view_entities) in &mut visible_entities.entities {
341 match frusta.frusta.get(view) {
342 Some(view_frusta) => {
343 cascade_view_entities.resize(view_frusta.len(), Default::default());
344 cascade_view_entities.iter_mut().for_each(|x| x.clear());
345 }
346 None => views_to_remove.push(*view),
347 };
348 }
349 for (view, frusta) in &frusta.frusta {
350 visible_entities
351 .entities
352 .entry(*view)
353 .or_insert_with(|| vec![VisibleMeshEntities::default(); frusta.len()]);
354 }
355
356 for v in views_to_remove {
357 visible_entities.entities.remove(&v);
358 }
359
360 if !directional_light.shadows_enabled || !light_view_visibility.get() {
362 continue;
363 }
364
365 let view_mask = maybe_view_mask.unwrap_or_default();
366
367 for (view, view_frusta) in &frusta.frusta {
368 visible_entity_query.par_iter().for_each_init(
369 || {
370 let mut entities = view_visible_entities_queue.borrow_local_mut();
371 entities.resize(view_frusta.len(), Vec::default());
372 (defer_visible_entities_queue.borrow_local_mut(), entities)
373 },
374 |(defer_visible_entities_local_queue, view_visible_entities_local_queue),
375 (
376 entity,
377 inherited_visibility,
378 maybe_entity_mask,
379 maybe_aabb,
380 maybe_transform,
381 has_visibility_range,
382 has_no_frustum_culling,
383 )| {
384 if !inherited_visibility.get() {
385 return;
386 }
387
388 let entity_mask = maybe_entity_mask.unwrap_or_default();
389 if !view_mask.intersects(entity_mask) {
390 return;
391 }
392
393 if has_visibility_range
395 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
396 !visible_entity_ranges.entity_is_in_range_of_view(entity, *view)
397 })
398 {
399 return;
400 }
401
402 if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
403 let mut visible = false;
404 for (frustum, frustum_visible_entities) in view_frusta
405 .iter()
406 .zip(view_visible_entities_local_queue.iter_mut())
407 {
408 if !has_no_frustum_culling
410 && !frustum.intersects_obb(aabb, &transform.affine(), false, true)
411 {
412 continue;
413 }
414 visible = true;
415
416 frustum_visible_entities.push(entity);
417 }
418 if visible {
419 defer_visible_entities_local_queue.push(entity);
420 }
421 } else {
422 defer_visible_entities_local_queue.push(entity);
423 for frustum_visible_entities in view_visible_entities_local_queue.iter_mut()
424 {
425 frustum_visible_entities.push(entity);
426 }
427 }
428 },
429 );
430 for entities in view_visible_entities_queue.iter_mut() {
432 visible_entities
433 .entities
434 .get_mut(view)
435 .unwrap()
436 .iter_mut()
437 .zip(entities.iter_mut())
438 .for_each(|(dst, source)| {
439 dst.append(source);
440 });
441 }
442 }
443
444 for (_, cascade_view_entities) in &mut visible_entities.entities {
445 cascade_view_entities
446 .iter_mut()
447 .map(DerefMut::deref_mut)
448 .for_each(shrink_entities);
449 }
450 }
451
452 let mut defer_queue = core::mem::take(defer_visible_entities_queue.deref_mut());
455 commands.queue(move |world: &mut World| {
456 let mut query = world.query::<&mut ViewVisibility>();
457 for entities in defer_queue.iter_mut() {
458 let mut iter = query.iter_many_mut(world, entities.iter());
459 while let Some(mut view_visibility) = iter.fetch_next() {
460 view_visibility.set_visible();
461 }
462 }
463 });
464}
465
466pub fn check_point_light_mesh_visibility(
467 visible_point_lights: Query<&VisibleClusterableObjects>,
468 mut point_lights: Query<(
469 &PointLight,
470 &GlobalTransform,
471 &CubemapFrusta,
472 &mut CubemapVisibleEntities,
473 Option<&RenderLayers>,
474 )>,
475 mut spot_lights: Query<(
476 &SpotLight,
477 &GlobalTransform,
478 &Frustum,
479 &mut VisibleMeshEntities,
480 Option<&RenderLayers>,
481 )>,
482 mut visible_entity_query: Query<
483 (
484 Entity,
485 &InheritedVisibility,
486 &mut ViewVisibility,
487 Option<&RenderLayers>,
488 Option<&Aabb>,
489 Option<&GlobalTransform>,
490 Has<VisibilityRange>,
491 Has<NoFrustumCulling>,
492 ),
493 (
494 Without<NotShadowCaster>,
495 Without<DirectionalLight>,
496 With<Mesh3d>,
497 ),
498 >,
499 visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
500 mut cubemap_visible_entities_queue: Local<Parallel<[Vec<Entity>; 6]>>,
501 mut spot_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
502 mut checked_lights: Local<EntityHashSet>,
503) {
504 checked_lights.clear();
505
506 let visible_entity_ranges = visible_entity_ranges.as_deref();
507 for visible_lights in &visible_point_lights {
508 for light_entity in visible_lights.entities.iter().copied() {
509 if !checked_lights.insert(light_entity) {
510 continue;
511 }
512
513 if let Ok((
515 point_light,
516 transform,
517 cubemap_frusta,
518 mut cubemap_visible_entities,
519 maybe_view_mask,
520 )) = point_lights.get_mut(light_entity)
521 {
522 for visible_entities in cubemap_visible_entities.iter_mut() {
523 visible_entities.entities.clear();
524 }
525
526 if !point_light.shadows_enabled {
528 continue;
529 }
530
531 let view_mask = maybe_view_mask.unwrap_or_default();
532 let light_sphere = Sphere {
533 center: Vec3A::from(transform.translation()),
534 radius: point_light.range,
535 };
536
537 visible_entity_query.par_iter_mut().for_each_init(
538 || cubemap_visible_entities_queue.borrow_local_mut(),
539 |cubemap_visible_entities_local_queue,
540 (
541 entity,
542 inherited_visibility,
543 mut view_visibility,
544 maybe_entity_mask,
545 maybe_aabb,
546 maybe_transform,
547 has_visibility_range,
548 has_no_frustum_culling,
549 )| {
550 if !inherited_visibility.get() {
551 return;
552 }
553 let entity_mask = maybe_entity_mask.unwrap_or_default();
554 if !view_mask.intersects(entity_mask) {
555 return;
556 }
557 if has_visibility_range
558 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
559 !visible_entity_ranges.entity_is_in_range_of_any_view(entity)
560 })
561 {
562 return;
563 }
564
565 if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
567 let model_to_world = transform.affine();
568 if !has_no_frustum_culling
570 && !light_sphere.intersects_obb(aabb, &model_to_world)
571 {
572 return;
573 }
574
575 for (frustum, visible_entities) in cubemap_frusta
576 .iter()
577 .zip(cubemap_visible_entities_local_queue.iter_mut())
578 {
579 if has_no_frustum_culling
580 || frustum.intersects_obb(aabb, &model_to_world, true, true)
581 {
582 view_visibility.set_visible();
583 visible_entities.push(entity);
584 }
585 }
586 } else {
587 view_visibility.set_visible();
588 for visible_entities in cubemap_visible_entities_local_queue.iter_mut()
589 {
590 visible_entities.push(entity);
591 }
592 }
593 },
594 );
595
596 for entities in cubemap_visible_entities_queue.iter_mut() {
597 for (dst, source) in
598 cubemap_visible_entities.iter_mut().zip(entities.iter_mut())
599 {
600 dst.entities.append(source);
601 }
602 }
603
604 for visible_entities in cubemap_visible_entities.iter_mut() {
605 shrink_entities(visible_entities);
606 }
607 }
608
609 if let Ok((point_light, transform, frustum, mut visible_entities, maybe_view_mask)) =
611 spot_lights.get_mut(light_entity)
612 {
613 visible_entities.clear();
614
615 if !point_light.shadows_enabled {
617 continue;
618 }
619
620 let view_mask = maybe_view_mask.unwrap_or_default();
621 let light_sphere = Sphere {
622 center: Vec3A::from(transform.translation()),
623 radius: point_light.range,
624 };
625
626 visible_entity_query.par_iter_mut().for_each_init(
627 || spot_visible_entities_queue.borrow_local_mut(),
628 |spot_visible_entities_local_queue,
629 (
630 entity,
631 inherited_visibility,
632 mut view_visibility,
633 maybe_entity_mask,
634 maybe_aabb,
635 maybe_transform,
636 has_visibility_range,
637 has_no_frustum_culling,
638 )| {
639 if !inherited_visibility.get() {
640 return;
641 }
642
643 let entity_mask = maybe_entity_mask.unwrap_or_default();
644 if !view_mask.intersects(entity_mask) {
645 return;
646 }
647 if has_visibility_range
649 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
650 !visible_entity_ranges.entity_is_in_range_of_any_view(entity)
651 })
652 {
653 return;
654 }
655
656 if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
657 let model_to_world = transform.affine();
658 if !has_no_frustum_culling
660 && !light_sphere.intersects_obb(aabb, &model_to_world)
661 {
662 return;
663 }
664
665 if has_no_frustum_culling
666 || frustum.intersects_obb(aabb, &model_to_world, true, true)
667 {
668 view_visibility.set_visible();
669 spot_visible_entities_local_queue.push(entity);
670 }
671 } else {
672 view_visibility.set_visible();
673 spot_visible_entities_local_queue.push(entity);
674 }
675 },
676 );
677
678 for entities in spot_visible_entities_queue.iter_mut() {
679 visible_entities.append(entities);
680 }
681
682 shrink_entities(visible_entities.deref_mut());
683 }
684 }
685 }
686}