extern crate alloc;
use bevy_app::{App, Plugin, PostUpdate, Update};
use bevy_asset::{AssetApp, AssetEventSystems};
use bevy_camera::{
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere},
visibility::{
CascadesVisibleEntities, CubemapVisibleEntities, InheritedVisibility, NoCpuCulling,
NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange, VisibilitySystems,
VisibleEntities, VisibleEntityRanges, VisibleMeshEntities,
},
Camera, Camera3d, CameraUpdateSystems, RenderTarget, ShadowLodOrigin,
};
use bevy_ecs::{entity::EntityHashSet, prelude::*, system::QueryLens};
#[cfg(feature = "bevy_gizmos")]
use bevy_gizmos::frustum::FrustumGizmoSystems;
use bevy_log::warn_once;
use bevy_math::Vec3A;
use bevy_mesh::Mesh3d;
use bevy_reflect::prelude::*;
use bevy_transform::{components::GlobalTransform, TransformSystems};
use bevy_utils::Parallel;
use core::{any::TypeId, mem, ops::DerefMut};
use smallvec::{smallvec, SmallVec};
pub mod cluster;
use cluster::assign::assign_objects_to_clusters;
pub use cluster::ClusteredDecal;
mod ambient_light;
pub use ambient_light::{AmbientLight, GlobalAmbientLight};
use bevy_camera::visibility::SetViewVisibility;
mod probe;
pub use probe::{
automatically_add_parallax_correction_components, AtmosphereEnvironmentMapLight,
EnvironmentMapLight, GeneratedEnvironmentMapLight, IrradianceVolume, LightProbe,
ParallaxCorrection, Skybox,
};
pub mod atmosphere;
pub use atmosphere::Atmosphere;
mod volumetric;
pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight};
pub mod cascade;
use cascade::build_directional_light_cascades;
pub use cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
mod point_light;
pub use point_light::{
update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture,
};
mod spot_light;
pub use spot_light::{
orthonormalize, spot_light_clip_from_view, spot_light_world_from_view,
update_spot_light_frusta, SpotLight, SpotLightTexture,
};
mod directional_light;
pub use directional_light::{
update_directional_light_frusta, DirectionalLight, DirectionalLightShadowMap,
DirectionalLightTexture, SunDisk,
};
mod rect_light;
pub use rect_light::RectLight;
#[cfg(feature = "bevy_gizmos")]
pub mod gizmos;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight,
GeneratedEnvironmentMapLight, GlobalAmbientLight, LightProbe, PointLight, RectLight,
SpotLight,
};
#[doc(hidden)]
#[cfg(feature = "bevy_gizmos")]
pub use crate::gizmos::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo};
}
use crate::{
atmosphere::{extract_chromatic_phase_textures, ScatteringMedium},
cluster::{add_light_probe_and_decal_aabbs, ClusterVisibilityClass, Clusters},
directional_light::validate_shadow_map_size,
point_light::update_point_light_bounding_spheres,
spot_light::update_spot_light_bounding_spheres,
};
pub mod light_consts {
pub mod lumens {
pub const LUMENS_PER_LED_WATTS: f32 = 90.0;
pub const LUMENS_PER_INCANDESCENT_WATTS: f32 = 13.8;
pub const LUMENS_PER_HALOGEN_WATTS: f32 = 19.8;
pub const VERY_LARGE_CINEMA_LIGHT: f32 = 1_000_000.0;
}
pub mod lux {
pub const MOONLESS_NIGHT: f32 = 0.0001;
pub const FULL_MOON_NIGHT: f32 = 0.05;
pub const CIVIL_TWILIGHT: f32 = 3.4;
pub const LIVING_ROOM: f32 = 50.;
pub const HALLWAY: f32 = 80.;
pub const DARK_OVERCAST_DAY: f32 = 100.;
pub const OFFICE: f32 = 320.;
pub const CLEAR_SUNRISE: f32 = 400.;
pub const OVERCAST_DAY: f32 = 1000.;
pub const AMBIENT_DAYLIGHT: f32 = 10_000.;
pub const FULL_DAYLIGHT: f32 = 20_000.;
pub const DIRECT_SUNLIGHT: f32 = 100_000.;
pub const RAW_SUNLIGHT: f32 = 130_000.;
}
}
#[derive(Default)]
pub struct LightPlugin;
impl Plugin for LightPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<GlobalAmbientLight>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
.init_asset::<ScatteringMedium>()
.register_required_components::<Camera3d, Clusters>()
.configure_sets(
PostUpdate,
SimulationLightSystems::CheckLightVisibility
.ambiguous_with(SimulationLightSystems::CheckLightVisibility),
)
.add_systems(Update, automatically_add_parallax_correction_components)
.add_systems(
PostUpdate,
(
validate_shadow_map_size.before(build_directional_light_cascades),
assign_objects_to_clusters
.in_set(SimulationLightSystems::AssignLightsToClusters)
.after(TransformSystems::Propagate)
.after(VisibilitySystems::CheckVisibility)
.after(CameraUpdateSystems),
update_directional_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(VisibilitySystems::CheckVisibility)
.after(TransformSystems::Propagate)
.after(SimulationLightSystems::UpdateDirectionalLightCascades)
.ambiguous_with(update_spot_light_frusta),
update_point_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystems::Propagate)
.after(SimulationLightSystems::AssignLightsToClusters),
#[cfg(feature = "bevy_gizmos")]
update_spot_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.before(FrustumGizmoSystems)
.after(TransformSystems::Propagate)
.after(SimulationLightSystems::AssignLightsToClusters),
#[cfg(not(feature = "bevy_gizmos"))]
update_spot_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystems::Propagate)
.after(SimulationLightSystems::AssignLightsToClusters),
(
check_dir_light_mesh_visibility,
check_point_light_mesh_visibility,
)
.in_set(SimulationLightSystems::CheckLightVisibility)
.after(VisibilitySystems::CalculateBounds)
.after(TransformSystems::Propagate)
.after(SimulationLightSystems::UpdateLightFrusta)
.after(VisibilitySystems::CheckVisibility)
.before(VisibilitySystems::MarkNewlyHiddenEntitiesInvisible),
(
update_point_light_bounding_spheres.after(TransformSystems::Propagate),
update_spot_light_bounding_spheres.after(TransformSystems::Propagate),
add_light_probe_and_decal_aabbs,
)
.in_set(SimulationLightSystems::UpdateBounds)
.before(VisibilitySystems::UpdateFrusta),
build_directional_light_cascades
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
.after(TransformSystems::Propagate)
.after(CameraUpdateSystems),
extract_chromatic_phase_textures.after(AssetEventSystems),
),
);
#[cfg(feature = "bevy_gizmos")]
app.add_plugins(gizmos::LightGizmoPlugin);
}
}
pub type WithLight = Or<(
With<PointLight>,
With<SpotLight>,
With<DirectionalLight>,
With<RectLight>,
)>;
#[derive(Debug, Component, Reflect, Default, Clone, PartialEq)]
#[reflect(Component, Default, Debug, Clone, PartialEq)]
pub struct NotShadowCaster;
#[derive(Debug, Component, Reflect, Default, Clone)]
#[reflect(Component, Default, Debug)]
pub struct NotShadowReceiver;
#[derive(Debug, Component, Reflect, Default, Clone)]
#[reflect(Component, Default, Debug)]
pub struct TransmittedShadowReceiver;
#[derive(Debug, Component, Reflect, Clone, Copy, PartialEq, Eq, Default)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub enum ShadowFilteringMethod {
Hardware2x2,
#[default]
Gaussian,
Temporal,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum SimulationLightSystems {
UpdateBounds,
AssignLightsToClusters,
UpdateDirectionalLightCascades,
UpdateLightFrusta,
CheckLightVisibility,
}
pub fn check_dir_light_mesh_visibility(
mut commands: Commands,
mut directional_lights: Query<
(
&DirectionalLight,
&CascadesFrusta,
&mut CascadesVisibleEntities,
Option<&RenderLayers>,
&ViewVisibility,
),
Without<SpotLight>,
>,
visible_entity_query: Query<
(
Entity,
&InheritedVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
Option<&GlobalTransform>,
Has<VisibilityRange>,
Has<NoFrustumCulling>,
),
(
Without<NotShadowCaster>,
Without<DirectionalLight>,
Without<NoCpuCulling>,
With<Mesh3d>,
),
>,
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
mut defer_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
mut view_visible_entities_queue: Local<Parallel<Vec<Vec<Entity>>>>,
) {
let visible_entity_ranges = visible_entity_ranges.as_deref();
for (directional_light, frusta, mut visible_entities, maybe_view_mask, light_view_visibility) in
&mut directional_lights
{
let mut views_to_remove = Vec::new();
for (view, cascade_view_entities) in &mut visible_entities.entities {
match frusta.frusta.get(view) {
Some(view_frusta) => {
cascade_view_entities.resize(view_frusta.len(), Default::default());
}
None => views_to_remove.push(*view),
};
}
for (view, frusta) in &frusta.frusta {
visible_entities
.entities
.entry(*view)
.or_insert_with(|| vec![VisibleMeshEntities::default(); frusta.len()]);
}
for v in views_to_remove {
visible_entities.entities.remove(&v);
}
if !directional_light.shadow_maps_enabled || !light_view_visibility.get() {
visible_entities.entities.clear();
continue;
}
let view_mask = maybe_view_mask.unwrap_or_default();
for (view, view_frusta) in &frusta.frusta {
visible_entity_query.par_iter().for_each_init(
|| {
let mut entities = view_visible_entities_queue.borrow_local_mut();
entities.resize(view_frusta.len(), Vec::default());
(defer_visible_entities_queue.borrow_local_mut(), entities)
},
|(defer_visible_entities_local_queue, view_visible_entities_local_queue),
(
entity,
inherited_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
has_no_frustum_culling,
)| {
if !inherited_visibility.get() {
return;
}
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
return;
}
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
!visible_entity_ranges.entity_is_in_range_of_view(entity, *view)
})
{
return;
}
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let mut visible = false;
for (frustum, frustum_visible_entities) in view_frusta
.iter()
.zip(view_visible_entities_local_queue.iter_mut())
{
if !has_no_frustum_culling
&& !frustum.intersects_obb(aabb, &transform.affine(), false, true)
{
continue;
}
visible = true;
frustum_visible_entities.push(entity);
}
if visible {
defer_visible_entities_local_queue.push(entity);
}
} else {
defer_visible_entities_local_queue.push(entity);
for frustum_visible_entities in view_visible_entities_local_queue.iter_mut()
{
frustum_visible_entities.push(entity);
}
}
},
);
for (view_dest_index, view_dest) in visible_entities
.entities
.get_mut(view)
.unwrap()
.iter_mut()
.enumerate()
{
view_dest.entities.clear();
for thread_entity_queue in view_visible_entities_queue.iter_mut() {
view_dest
.entities
.append(&mut thread_entity_queue[view_dest_index]);
}
view_dest.shrink();
view_dest.entities.sort_unstable();
}
}
}
let mut defer_queue = mem::take(defer_visible_entities_queue.deref_mut());
commands.queue(move |world: &mut World| {
let mut query = world.query::<&mut ViewVisibility>();
for entities in defer_queue.iter_mut() {
let mut iter = query.iter_many_mut(world, entities.iter());
while let Some(mut view_visibility) = iter.fetch_next() {
view_visibility.set_visible();
}
}
});
}
pub fn check_point_light_mesh_visibility(
visible_point_lights: Query<&VisibleEntities>,
mut point_lights: Query<(
&PointLight,
&GlobalTransform,
&CubemapFrusta,
&mut CubemapVisibleEntities,
Option<&RenderLayers>,
)>,
mut spot_lights: Query<(
&SpotLight,
&GlobalTransform,
&Frustum,
&mut VisibleMeshEntities,
Option<&RenderLayers>,
)>,
mut visible_entity_query: Query<
(
Entity,
&InheritedVisibility,
&mut ViewVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
Option<&GlobalTransform>,
Has<VisibilityRange>,
Has<NoFrustumCulling>,
),
(
Without<NotShadowCaster>,
Without<DirectionalLight>,
Without<NoCpuCulling>,
With<Mesh3d>,
),
>,
mut camera_query: Query<(Entity, &RenderTarget), With<Camera>>,
mut shadow_lod_origin_query: Query<Entity, With<ShadowLodOrigin>>,
mut point_and_spot_light_query: Query<Entity, Or<(With<PointLight>, With<SpotLight>)>>,
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
mut cubemap_visible_entities_queue: Local<Parallel<[Vec<Entity>; 6]>>,
mut spot_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
mut checked_lights: Local<EntityHashSet>,
) {
checked_lights.clear();
let shadow_lod_origin = get_shadow_lod_origin(
camera_query.transmute_lens_filtered(),
shadow_lod_origin_query.transmute_lens_filtered(),
point_and_spot_light_query.transmute_lens_filtered(),
);
let visible_entity_ranges = visible_entity_ranges.as_deref();
for visible_lights in &visible_point_lights {
for &light_entity in visible_lights.get(TypeId::of::<ClusterVisibilityClass>()) {
if !checked_lights.insert(light_entity) {
continue;
}
if let Ok((
point_light,
transform,
cubemap_frusta,
mut cubemap_visible_entities,
maybe_view_mask,
)) = point_lights.get_mut(light_entity)
{
if !point_light.shadow_maps_enabled {
continue;
}
let view_mask = maybe_view_mask.unwrap_or_default();
let light_sphere = Sphere {
center: Vec3A::from(transform.translation()),
radius: point_light.range,
};
visible_entity_query.par_iter_mut().for_each_init(
|| cubemap_visible_entities_queue.borrow_local_mut(),
|cubemap_visible_entities_local_queue,
(
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
has_no_frustum_culling,
)| {
if !inherited_visibility.get() {
return;
}
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
return;
}
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
shadow_lod_origin.is_none_or(|shadow_lod_origin| {
!visible_entity_ranges
.entity_is_in_range_of_view(entity, shadow_lod_origin)
})
})
{
return;
}
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let model_to_world = transform.affine();
if !has_no_frustum_culling
&& !light_sphere.intersects_obb(aabb, &model_to_world)
{
return;
}
for (frustum, visible_entities) in cubemap_frusta
.iter()
.zip(cubemap_visible_entities_local_queue.iter_mut())
{
if has_no_frustum_culling
|| frustum.intersects_obb(aabb, &model_to_world, true, true)
{
view_visibility.set_visible();
visible_entities.push(entity);
}
}
} else {
view_visibility.set_visible();
for visible_entities in cubemap_visible_entities_local_queue.iter_mut()
{
visible_entities.push(entity);
}
}
},
);
for (view_dest_index, view_dest) in cubemap_visible_entities.iter_mut().enumerate()
{
view_dest.entities.clear();
for thread_entity_queue in cubemap_visible_entities_queue.iter_mut() {
view_dest
.entities
.append(&mut thread_entity_queue[view_dest_index]);
}
view_dest.shrink();
view_dest.entities.sort_unstable();
}
}
if let Ok((point_light, transform, frustum, mut visible_entities, maybe_view_mask)) =
spot_lights.get_mut(light_entity)
{
if !point_light.shadow_maps_enabled {
continue;
}
let view_mask = maybe_view_mask.unwrap_or_default();
let light_sphere = Sphere {
center: Vec3A::from(transform.translation()),
radius: point_light.range,
};
visible_entity_query.par_iter_mut().for_each_init(
|| spot_visible_entities_queue.borrow_local_mut(),
|spot_visible_entities_local_queue,
(
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
has_no_frustum_culling,
)| {
if !inherited_visibility.get() {
return;
}
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
return;
}
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
shadow_lod_origin.is_none_or(|shadow_lod_origin| {
!visible_entity_ranges
.entity_is_in_range_of_view(entity, shadow_lod_origin)
})
})
{
return;
}
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let model_to_world = transform.affine();
if !has_no_frustum_culling
&& !light_sphere.intersects_obb(aabb, &model_to_world)
{
return;
}
if has_no_frustum_culling
|| frustum.intersects_obb(aabb, &model_to_world, true, true)
{
view_visibility.set_visible();
spot_visible_entities_local_queue.push(entity);
}
} else {
view_visibility.set_visible();
spot_visible_entities_local_queue.push(entity);
}
},
);
visible_entities.entities.clear();
for thread_entity_queue in spot_visible_entities_queue.iter_mut() {
visible_entities.entities.append(thread_entity_queue);
}
visible_entities.shrink();
visible_entities.entities.sort_unstable();
}
}
}
}
pub fn get_shadow_lod_origin(
mut camera_query: QueryLens<(Entity, &RenderTarget), With<Camera>>,
mut shadow_lod_origin_query: QueryLens<Entity, With<ShadowLodOrigin>>,
mut lights_query: QueryLens<Entity, Or<(With<PointLight>, With<SpotLight>)>>,
) -> Option<Entity> {
let (camera_query, shadow_lod_origin_query) =
(camera_query.query(), shadow_lod_origin_query.query());
let mut entities: SmallVec<[Entity; 4]> = smallvec![];
entities.extend(shadow_lod_origin_query.iter());
if let Some(lod_origin) = entities.iter().min() {
return Some(*lod_origin);
}
entities.extend(
camera_query
.iter()
.filter_map(|(main_entity, render_target)| match *render_target {
RenderTarget::Window(_) => Some(main_entity),
_ => None,
}),
);
if let Some(lod_origin) = entities.iter().min() {
return Some(*lod_origin);
};
entities.extend(camera_query.iter().map(|(main_entity, _)| main_entity));
if let Some(lod_origin) = entities.iter().min() {
if !lights_query.query().is_empty() {
warn_once!(
"Point lights and/or spot lights are present, but no entity has \
`ShadowLodOrigin`, and no camera that renders to the window has been found. \
Consider using the `ShadowLodOrigin` component to set a LOD origin."
);
}
return Some(*lod_origin);
};
None
}