1extern crate alloc;
6
7use bevy_app::{App, Plugin, PostUpdate, Update};
8use bevy_asset::{AssetApp, AssetEventSystems};
9use bevy_camera::{
10 primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere},
11 visibility::{
12 CascadesVisibleEntities, CubemapVisibleEntities, InheritedVisibility, NoCpuCulling,
13 NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange, VisibilitySystems,
14 VisibleEntities, VisibleEntityRanges, VisibleMeshEntities,
15 },
16 Camera, Camera3d, CameraUpdateSystems, RenderTarget, ShadowLodOrigin,
17};
18use bevy_ecs::{entity::EntityHashSet, prelude::*, system::QueryLens};
19#[cfg(feature = "bevy_gizmos")]
20use bevy_gizmos::frustum::FrustumGizmoSystems;
21use bevy_log::warn_once;
22use bevy_math::Vec3A;
23use bevy_mesh::Mesh3d;
24use bevy_reflect::prelude::*;
25use bevy_transform::{components::GlobalTransform, TransformSystems};
26use bevy_utils::Parallel;
27use core::{any::TypeId, mem, ops::DerefMut};
28use smallvec::{smallvec, SmallVec};
29
30pub mod cluster;
31use cluster::assign::assign_objects_to_clusters;
32pub use cluster::ClusteredDecal;
33mod ambient_light;
34pub use ambient_light::{AmbientLight, GlobalAmbientLight};
35use bevy_camera::visibility::SetViewVisibility;
36
37mod probe;
38pub use probe::{
39 automatically_add_parallax_correction_components, AtmosphereEnvironmentMapLight,
40 EnvironmentMapLight, GeneratedEnvironmentMapLight, IrradianceVolume, LightProbe,
41 ParallaxCorrection, Skybox,
42};
43pub mod atmosphere;
44pub use atmosphere::Atmosphere;
45mod volumetric;
46pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight};
47pub mod cascade;
48use cascade::build_directional_light_cascades;
49pub use cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
50mod point_light;
51pub use point_light::{
52 update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture,
53};
54mod spot_light;
55pub use spot_light::{
56 orthonormalize, spot_light_clip_from_view, spot_light_world_from_view,
57 update_spot_light_frusta, SpotLight, SpotLightTexture,
58};
59mod directional_light;
60pub use directional_light::{
61 update_directional_light_frusta, DirectionalLight, DirectionalLightShadowMap,
62 DirectionalLightTexture, SunDisk,
63};
64mod rect_light;
65pub use rect_light::RectLight;
66#[cfg(feature = "bevy_gizmos")]
68pub mod gizmos;
69
70pub mod prelude {
74 #[doc(hidden)]
75 pub use crate::{
76 light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight,
77 GeneratedEnvironmentMapLight, GlobalAmbientLight, LightProbe, PointLight, RectLight,
78 SpotLight,
79 };
80
81 #[doc(hidden)]
82 #[cfg(feature = "bevy_gizmos")]
83 pub use crate::gizmos::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo};
84}
85
86use crate::{
87 atmosphere::{extract_chromatic_phase_textures, ScatteringMedium},
88 cluster::{add_light_probe_and_decal_aabbs, ClusterVisibilityClass, Clusters},
89 directional_light::validate_shadow_map_size,
90 point_light::update_point_light_bounding_spheres,
91 spot_light::update_spot_light_bounding_spheres,
92};
93
94pub mod light_consts {
96 pub mod lumens {
108 pub const LUMENS_PER_LED_WATTS: f32 = 90.0;
110 pub const LUMENS_PER_INCANDESCENT_WATTS: f32 = 13.8;
112 pub const LUMENS_PER_HALOGEN_WATTS: f32 = 19.8;
114 pub const VERY_LARGE_CINEMA_LIGHT: f32 = 1_000_000.0;
118 }
119
120 pub mod lux {
131 pub const MOONLESS_NIGHT: f32 = 0.0001;
133 pub const FULL_MOON_NIGHT: f32 = 0.05;
135 pub const CIVIL_TWILIGHT: f32 = 3.4;
137 pub const LIVING_ROOM: f32 = 50.;
139 pub const HALLWAY: f32 = 80.;
141 pub const DARK_OVERCAST_DAY: f32 = 100.;
143 pub const OFFICE: f32 = 320.;
145 pub const CLEAR_SUNRISE: f32 = 400.;
147 pub const OVERCAST_DAY: f32 = 1000.;
149 pub const AMBIENT_DAYLIGHT: f32 = 10_000.;
152 pub const FULL_DAYLIGHT: f32 = 20_000.;
154 pub const DIRECT_SUNLIGHT: f32 = 100_000.;
156 pub const RAW_SUNLIGHT: f32 = 130_000.;
158 }
159}
160
161#[derive(Default)]
163pub struct LightPlugin;
164
165impl Plugin for LightPlugin {
166 fn build(&self, app: &mut App) {
167 app.init_resource::<GlobalAmbientLight>()
168 .init_resource::<DirectionalLightShadowMap>()
169 .init_resource::<PointLightShadowMap>()
170 .init_asset::<ScatteringMedium>()
171 .register_required_components::<Camera3d, Clusters>()
172 .configure_sets(
173 PostUpdate,
174 SimulationLightSystems::CheckLightVisibility
175 .ambiguous_with(SimulationLightSystems::CheckLightVisibility),
176 )
177 .configure_sets(
178 PostUpdate,
179 SimulationLightSystems::AssignLightsToClusters
180 .before(bevy_app::TransformGizmoRenderStep),
181 )
182 .add_systems(Update, automatically_add_parallax_correction_components)
183 .add_systems(
184 PostUpdate,
185 (
186 validate_shadow_map_size.before(build_directional_light_cascades),
187 assign_objects_to_clusters
188 .in_set(SimulationLightSystems::AssignLightsToClusters)
189 .after(TransformSystems::Propagate)
190 .after(VisibilitySystems::CheckVisibility)
191 .after(CameraUpdateSystems),
192 update_directional_light_frusta
193 .in_set(SimulationLightSystems::UpdateLightFrusta)
194 .after(VisibilitySystems::CheckVisibility)
196 .after(TransformSystems::Propagate)
197 .after(SimulationLightSystems::UpdateDirectionalLightCascades)
198 .ambiguous_with(update_spot_light_frusta),
202 update_point_light_frusta
203 .in_set(SimulationLightSystems::UpdateLightFrusta)
204 .after(TransformSystems::Propagate)
205 .after(SimulationLightSystems::AssignLightsToClusters),
206 #[cfg(feature = "bevy_gizmos")]
207 update_spot_light_frusta
208 .in_set(SimulationLightSystems::UpdateLightFrusta)
209 .before(FrustumGizmoSystems)
210 .after(TransformSystems::Propagate)
211 .after(SimulationLightSystems::AssignLightsToClusters),
212 #[cfg(not(feature = "bevy_gizmos"))]
213 update_spot_light_frusta
214 .in_set(SimulationLightSystems::UpdateLightFrusta)
215 .after(TransformSystems::Propagate)
216 .after(SimulationLightSystems::AssignLightsToClusters),
217 (
218 check_dir_light_mesh_visibility,
219 check_point_light_mesh_visibility,
220 )
221 .in_set(SimulationLightSystems::CheckLightVisibility)
222 .after(VisibilitySystems::CalculateBounds)
223 .after(TransformSystems::Propagate)
224 .after(SimulationLightSystems::UpdateLightFrusta)
225 .after(VisibilitySystems::CheckVisibility)
230 .before(VisibilitySystems::MarkNewlyHiddenEntitiesInvisible),
231 (
232 update_point_light_bounding_spheres.after(TransformSystems::Propagate),
233 update_spot_light_bounding_spheres.after(TransformSystems::Propagate),
234 add_light_probe_and_decal_aabbs,
235 )
236 .in_set(SimulationLightSystems::UpdateBounds)
237 .before(VisibilitySystems::UpdateFrusta),
238 build_directional_light_cascades
239 .in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
240 .after(TransformSystems::Propagate)
241 .before(bevy_app::TransformGizmoRenderStep)
242 .after(CameraUpdateSystems),
243 extract_chromatic_phase_textures.after(AssetEventSystems),
244 ),
245 );
246
247 #[cfg(feature = "bevy_gizmos")]
248 app.add_plugins(gizmos::LightGizmoPlugin);
249 }
250}
251
252pub type WithLight = Or<(
255 With<PointLight>,
256 With<SpotLight>,
257 With<DirectionalLight>,
258 With<RectLight>,
259)>;
260
261#[derive(Debug, Component, Reflect, Default, Clone, PartialEq)]
263#[reflect(Component, Default, Debug, Clone, PartialEq)]
264pub struct NotShadowCaster;
265#[derive(Debug, Component, Reflect, Default, Clone)]
271#[reflect(Component, Default, Debug)]
272pub struct NotShadowReceiver;
273#[derive(Debug, Component, Reflect, Default, Clone)]
281#[reflect(Component, Default, Debug)]
282pub struct TransmittedShadowReceiver;
283
284#[derive(Debug, Component, Reflect, Clone, Copy, PartialEq, Eq, Default)]
290#[reflect(Component, Default, Debug, PartialEq, Clone)]
291pub enum ShadowFilteringMethod {
292 Hardware2x2,
296 #[default]
306 Gaussian,
307 Temporal,
318}
319
320#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
322pub enum SimulationLightSystems {
323 UpdateBounds,
325 AssignLightsToClusters,
327 UpdateDirectionalLightCascades,
329 UpdateLightFrusta,
331 CheckLightVisibility,
335}
336
337pub fn check_dir_light_mesh_visibility(
343 mut commands: Commands,
344 mut directional_lights: Query<
345 (
346 &DirectionalLight,
347 &CascadesFrusta,
348 &mut CascadesVisibleEntities,
349 Option<&RenderLayers>,
350 &ViewVisibility,
351 ),
352 Without<SpotLight>,
353 >,
354 visible_entity_query: Query<
355 (
356 Entity,
357 &InheritedVisibility,
358 Option<&RenderLayers>,
359 Option<&Aabb>,
360 Option<&GlobalTransform>,
361 Has<VisibilityRange>,
362 Has<NoFrustumCulling>,
363 ),
364 (
365 Without<NotShadowCaster>,
366 Without<DirectionalLight>,
367 Without<NoCpuCulling>,
368 With<Mesh3d>,
369 ),
370 >,
371 visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
372 mut defer_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
373 mut view_visible_entities_queue: Local<Parallel<Vec<Vec<Entity>>>>,
374) {
375 let visible_entity_ranges = visible_entity_ranges.as_deref();
376
377 for (directional_light, frusta, mut visible_entities, maybe_view_mask, light_view_visibility) in
378 &mut directional_lights
379 {
380 let mut views_to_remove = Vec::new();
381 for (view, cascade_view_entities) in &mut visible_entities.entities {
382 match frusta.frusta.get(view) {
383 Some(view_frusta) => {
384 cascade_view_entities.resize(view_frusta.len(), Default::default());
385 }
386 None => views_to_remove.push(*view),
387 };
388 }
389 for (view, frusta) in &frusta.frusta {
390 visible_entities
391 .entities
392 .entry(*view)
393 .or_insert_with(|| vec![VisibleMeshEntities::default(); frusta.len()]);
394 }
395
396 for v in views_to_remove {
397 visible_entities.entities.remove(&v);
398 }
399
400 if !directional_light.shadow_maps_enabled || !light_view_visibility.get() {
402 visible_entities.entities.clear();
403 continue;
404 }
405
406 let view_mask = maybe_view_mask.unwrap_or_default();
407
408 for (view, view_frusta) in &frusta.frusta {
409 for thread_queue in view_visible_entities_queue.iter_mut() {
413 thread_queue.resize(view_frusta.len(), Vec::default());
414 }
415
416 visible_entity_query.par_iter().for_each_init(
417 || {
418 let mut entities = view_visible_entities_queue.borrow_local_mut();
419 entities.resize(view_frusta.len(), Vec::default());
420 (defer_visible_entities_queue.borrow_local_mut(), entities)
421 },
422 |(defer_visible_entities_local_queue, view_visible_entities_local_queue),
423 (
424 entity,
425 inherited_visibility,
426 maybe_entity_mask,
427 maybe_aabb,
428 maybe_transform,
429 has_visibility_range,
430 has_no_frustum_culling,
431 )| {
432 if !inherited_visibility.get() {
433 return;
434 }
435
436 let entity_mask = maybe_entity_mask.unwrap_or_default();
437 if !view_mask.intersects(entity_mask) {
438 return;
439 }
440
441 if has_visibility_range
443 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
444 !visible_entity_ranges.entity_is_in_range_of_view(entity, *view)
445 })
446 {
447 return;
448 }
449
450 if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
451 let mut visible = false;
452 for (frustum, frustum_visible_entities) in view_frusta
453 .iter()
454 .zip(view_visible_entities_local_queue.iter_mut())
455 {
456 if !has_no_frustum_culling
458 && !frustum.intersects_obb(aabb, &transform.affine(), false, true)
459 {
460 continue;
461 }
462 visible = true;
463
464 frustum_visible_entities.push(entity);
465 }
466 if visible {
467 defer_visible_entities_local_queue.push(entity);
468 }
469 } else {
470 defer_visible_entities_local_queue.push(entity);
471 for frustum_visible_entities in view_visible_entities_local_queue.iter_mut()
472 {
473 frustum_visible_entities.push(entity);
474 }
475 }
476 },
477 );
478 for (view_dest_index, view_dest) in visible_entities
480 .entities
481 .get_mut(view)
482 .unwrap()
483 .iter_mut()
484 .enumerate()
485 {
486 view_dest.entities.clear();
487 for thread_entity_queue in view_visible_entities_queue.iter_mut() {
488 view_dest
489 .entities
490 .append(&mut thread_entity_queue[view_dest_index]);
491 }
492 view_dest.shrink();
493 view_dest.entities.sort_unstable();
494 }
495 }
496 }
497
498 let mut defer_queue = mem::take(defer_visible_entities_queue.deref_mut());
501 commands.queue(move |world: &mut World| {
502 let mut query = world.query::<&mut ViewVisibility>();
503 for entities in defer_queue.iter_mut() {
504 let mut iter = query.iter_many_mut(world, entities.iter()).matched();
505 while let Some(mut view_visibility) = iter.fetch_next() {
506 view_visibility.set_visible();
507 }
508 }
509 });
510}
511
512pub fn check_point_light_mesh_visibility(
518 visible_point_lights: Query<&VisibleEntities>,
519 mut point_lights: Query<(
520 &PointLight,
521 &GlobalTransform,
522 &CubemapFrusta,
523 &mut CubemapVisibleEntities,
524 Option<&RenderLayers>,
525 )>,
526 mut spot_lights: Query<(
527 &SpotLight,
528 &GlobalTransform,
529 &Frustum,
530 &mut VisibleMeshEntities,
531 Option<&RenderLayers>,
532 )>,
533 mut visible_entity_query: Query<
534 (
535 Entity,
536 &InheritedVisibility,
537 &mut ViewVisibility,
538 Option<&RenderLayers>,
539 Option<&Aabb>,
540 Option<&GlobalTransform>,
541 Has<VisibilityRange>,
542 Has<NoFrustumCulling>,
543 ),
544 (
545 Without<NotShadowCaster>,
546 Without<DirectionalLight>,
547 Without<NoCpuCulling>,
548 With<Mesh3d>,
549 ),
550 >,
551 mut camera_query: Query<(Entity, &RenderTarget), With<Camera>>,
552 mut shadow_lod_origin_query: Query<Entity, With<ShadowLodOrigin>>,
553 mut point_and_spot_light_query: Query<Entity, Or<(With<PointLight>, With<SpotLight>)>>,
554 visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
555 mut cubemap_visible_entities_queue: Local<Parallel<[Vec<Entity>; 6]>>,
556 mut spot_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
557 mut checked_lights: Local<EntityHashSet>,
558) {
559 checked_lights.clear();
560
561 let shadow_lod_origin = get_shadow_lod_origin(
562 camera_query.transmute_lens_filtered(),
563 shadow_lod_origin_query.transmute_lens_filtered(),
564 point_and_spot_light_query.transmute_lens_filtered(),
565 );
566
567 let visible_entity_ranges = visible_entity_ranges.as_deref();
568 for visible_lights in &visible_point_lights {
569 for &light_entity in visible_lights.get(TypeId::of::<ClusterVisibilityClass>()) {
570 if !checked_lights.insert(light_entity) {
571 continue;
572 }
573
574 if let Ok((
576 point_light,
577 transform,
578 cubemap_frusta,
579 mut cubemap_visible_entities,
580 maybe_view_mask,
581 )) = point_lights.get_mut(light_entity)
582 {
583 if !point_light.shadow_maps_enabled {
585 continue;
586 }
587
588 let view_mask = maybe_view_mask.unwrap_or_default();
589 let light_sphere = Sphere {
590 center: Vec3A::from(transform.translation()),
591 radius: point_light.range,
592 };
593
594 visible_entity_query.par_iter_mut().for_each_init(
595 || cubemap_visible_entities_queue.borrow_local_mut(),
596 |cubemap_visible_entities_local_queue,
597 (
598 entity,
599 inherited_visibility,
600 mut view_visibility,
601 maybe_entity_mask,
602 maybe_aabb,
603 maybe_transform,
604 has_visibility_range,
605 has_no_frustum_culling,
606 )| {
607 if !inherited_visibility.get() {
608 return;
609 }
610 let entity_mask = maybe_entity_mask.unwrap_or_default();
611 if !view_mask.intersects(entity_mask) {
612 return;
613 }
614 if has_visibility_range
615 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
616 shadow_lod_origin.is_none_or(|shadow_lod_origin| {
617 !visible_entity_ranges
618 .entity_is_in_range_of_view(entity, shadow_lod_origin)
619 })
620 })
621 {
622 return;
623 }
624
625 if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
627 let model_to_world = transform.affine();
628 if !has_no_frustum_culling
630 && !light_sphere.intersects_obb(aabb, &model_to_world)
631 {
632 return;
633 }
634
635 for (frustum, visible_entities) in cubemap_frusta
636 .iter()
637 .zip(cubemap_visible_entities_local_queue.iter_mut())
638 {
639 if has_no_frustum_culling
640 || frustum.intersects_obb(aabb, &model_to_world, true, true)
641 {
642 view_visibility.set_visible();
643 visible_entities.push(entity);
644 }
645 }
646 } else {
647 view_visibility.set_visible();
648 for visible_entities in cubemap_visible_entities_local_queue.iter_mut()
649 {
650 visible_entities.push(entity);
651 }
652 }
653 },
654 );
655
656 for (view_dest_index, view_dest) in cubemap_visible_entities.iter_mut().enumerate()
658 {
659 view_dest.entities.clear();
660 for thread_entity_queue in cubemap_visible_entities_queue.iter_mut() {
661 view_dest
662 .entities
663 .append(&mut thread_entity_queue[view_dest_index]);
664 }
665 view_dest.shrink();
666 view_dest.entities.sort_unstable();
667 }
668 }
669
670 if let Ok((point_light, transform, frustum, mut visible_entities, maybe_view_mask)) =
672 spot_lights.get_mut(light_entity)
673 {
674 if !point_light.shadow_maps_enabled {
676 continue;
677 }
678
679 let view_mask = maybe_view_mask.unwrap_or_default();
680 let light_sphere = Sphere {
681 center: Vec3A::from(transform.translation()),
682 radius: point_light.range,
683 };
684
685 visible_entity_query.par_iter_mut().for_each_init(
686 || spot_visible_entities_queue.borrow_local_mut(),
687 |spot_visible_entities_local_queue,
688 (
689 entity,
690 inherited_visibility,
691 mut view_visibility,
692 maybe_entity_mask,
693 maybe_aabb,
694 maybe_transform,
695 has_visibility_range,
696 has_no_frustum_culling,
697 )| {
698 if !inherited_visibility.get() {
699 return;
700 }
701
702 let entity_mask = maybe_entity_mask.unwrap_or_default();
703 if !view_mask.intersects(entity_mask) {
704 return;
705 }
706 if has_visibility_range
708 && visible_entity_ranges.is_some_and(|visible_entity_ranges| {
709 shadow_lod_origin.is_none_or(|shadow_lod_origin| {
710 !visible_entity_ranges
711 .entity_is_in_range_of_view(entity, shadow_lod_origin)
712 })
713 })
714 {
715 return;
716 }
717
718 if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
719 let model_to_world = transform.affine();
720 if !has_no_frustum_culling
722 && !light_sphere.intersects_obb(aabb, &model_to_world)
723 {
724 return;
725 }
726
727 if has_no_frustum_culling
728 || frustum.intersects_obb(aabb, &model_to_world, true, true)
729 {
730 view_visibility.set_visible();
731 spot_visible_entities_local_queue.push(entity);
732 }
733 } else {
734 view_visibility.set_visible();
735 spot_visible_entities_local_queue.push(entity);
736 }
737 },
738 );
739
740 visible_entities.entities.clear();
741 for thread_entity_queue in spot_visible_entities_queue.iter_mut() {
742 visible_entities.entities.append(thread_entity_queue);
743 }
744 visible_entities.shrink();
745 visible_entities.entities.sort_unstable();
746 }
747 }
748 }
749}
750
751pub fn get_shadow_lod_origin(
761 mut camera_query: QueryLens<(Entity, &RenderTarget), With<Camera>>,
762 mut shadow_lod_origin_query: QueryLens<Entity, With<ShadowLodOrigin>>,
763 mut lights_query: QueryLens<Entity, Or<(With<PointLight>, With<SpotLight>)>>,
764) -> Option<Entity> {
765 let (camera_query, shadow_lod_origin_query) =
766 (camera_query.query(), shadow_lod_origin_query.query());
767
768 let mut entities: SmallVec<[Entity; 4]> = smallvec![];
769 entities.extend(shadow_lod_origin_query.iter());
770 if let Some(lod_origin) = entities.iter().min() {
771 return Some(*lod_origin);
772 }
773
774 entities.extend(
775 camera_query
776 .iter()
777 .filter_map(|(main_entity, render_target)| match *render_target {
778 RenderTarget::Window(_) => Some(main_entity),
779 _ => None,
780 }),
781 );
782 if let Some(lod_origin) = entities.iter().min() {
783 return Some(*lod_origin);
784 };
785
786 entities.extend(camera_query.iter().map(|(main_entity, _)| main_entity));
787 if let Some(lod_origin) = entities.iter().min() {
788 if !lights_query.query().is_empty() {
789 warn_once!(
790 "Point lights and/or spot lights are present, but no entity has \
791 `ShadowLodOrigin`, and no camera that renders to the window has been found. \
792 Consider using the `ShadowLodOrigin` component to set a LOD origin."
793 );
794 }
795
796 return Some(*lod_origin);
797 };
798
799 None
800}