Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_light/src/lib.rs
9331 views
1
//! Provides component types for lighting a bevy scene. This includes the usual
2
//! directional, point, and spot lights, as well as light probes, atmosphere,
3
//! other volumetrics, and shadow configuration.
4
5
extern crate alloc;
6
7
use bevy_app::{App, Plugin, PostUpdate, Update};
8
use bevy_asset::AssetApp;
9
use bevy_camera::{
10
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere},
11
visibility::{
12
CascadesVisibleEntities, CubemapVisibleEntities, InheritedVisibility, NoFrustumCulling,
13
RenderLayers, ViewVisibility, VisibilityRange, VisibilitySystems, VisibleEntityRanges,
14
VisibleMeshEntities,
15
},
16
Camera3d, CameraUpdateSystems,
17
};
18
use bevy_ecs::{entity::EntityHashSet, prelude::*};
19
#[cfg(feature = "bevy_gizmos")]
20
use bevy_gizmos::frustum::FrustumGizmoSystems;
21
use bevy_math::Vec3A;
22
use bevy_mesh::Mesh3d;
23
use bevy_reflect::prelude::*;
24
use bevy_transform::{components::GlobalTransform, TransformSystems};
25
use bevy_utils::Parallel;
26
use core::ops::DerefMut;
27
28
pub mod cluster;
29
pub use cluster::ClusteredDecal;
30
use cluster::{
31
assign::assign_objects_to_clusters, GlobalVisibleClusterableObjects, VisibleClusterableObjects,
32
};
33
mod ambient_light;
34
pub use ambient_light::{AmbientLight, GlobalAmbientLight};
35
use bevy_camera::visibility::SetViewVisibility;
36
37
mod probe;
38
pub use probe::{
39
automatically_add_parallax_correction_components, AtmosphereEnvironmentMapLight,
40
EnvironmentMapLight, GeneratedEnvironmentMapLight, IrradianceVolume, LightProbe,
41
ParallaxCorrection, Skybox,
42
};
43
pub mod atmosphere;
44
pub use atmosphere::Atmosphere;
45
mod volumetric;
46
pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight};
47
pub mod cascade;
48
use cascade::build_directional_light_cascades;
49
pub use cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
50
mod point_light;
51
pub use point_light::{
52
update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture,
53
};
54
mod spot_light;
55
pub use spot_light::{
56
orthonormalize, spot_light_clip_from_view, spot_light_world_from_view,
57
update_spot_light_frusta, SpotLight, SpotLightTexture,
58
};
59
mod directional_light;
60
pub use directional_light::{
61
update_directional_light_frusta, DirectionalLight, DirectionalLightShadowMap,
62
DirectionalLightTexture, SunDisk,
63
};
64
/// Provides gizmo drawing for visualizing light positions.
65
#[cfg(feature = "bevy_gizmos")]
66
pub mod gizmos;
67
68
/// The light prelude.
69
///
70
/// This includes the most common types in this crate, re-exported for your convenience.
71
pub mod prelude {
72
#[doc(hidden)]
73
pub use crate::{
74
light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight,
75
GeneratedEnvironmentMapLight, GlobalAmbientLight, LightProbe, PointLight, SpotLight,
76
};
77
78
#[doc(hidden)]
79
#[cfg(feature = "bevy_gizmos")]
80
pub use crate::gizmos::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo};
81
}
82
83
use crate::{
84
atmosphere::ScatteringMedium, cluster::Clusters, directional_light::validate_shadow_map_size,
85
};
86
87
/// Constants for operating with the light units: lumens, and lux.
88
pub mod light_consts {
89
/// Approximations for converting the wattage of lamps to lumens.
90
///
91
/// The **lumen** (symbol: **lm**) is the unit of [luminous flux], a measure
92
/// of the total quantity of [visible light] emitted by a source per unit of
93
/// time, in the [International System of Units] (SI).
94
///
95
/// For more information, see [wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit))
96
///
97
/// [luminous flux]: https://en.wikipedia.org/wiki/Luminous_flux
98
/// [visible light]: https://en.wikipedia.org/wiki/Visible_light
99
/// [International System of Units]: https://en.wikipedia.org/wiki/International_System_of_Units
100
pub mod lumens {
101
/// The conversion factor used to determine how many lumens a typical LED light of a given wattage produces.
102
pub const LUMENS_PER_LED_WATTS: f32 = 90.0;
103
/// The conversion factor used to determine how many lumens a typical incandescent light of a given wattage produces.
104
pub const LUMENS_PER_INCANDESCENT_WATTS: f32 = 13.8;
105
/// The conversion factor used to determine how many lumens a typical halogen light of a given wattage produces.
106
pub const LUMENS_PER_HALOGEN_WATTS: f32 = 19.8;
107
/// 1,000,000 lumens is a very large "cinema light" capable of registering brightly at Bevy's
108
/// default [`bevy_camera::Exposure::BLENDER`] exposure level. For "indoor lighting" with a lower exposure,
109
/// this would be way too bright.
110
pub const VERY_LARGE_CINEMA_LIGHT: f32 = 1_000_000.0;
111
}
112
113
/// Predefined for lux values in several locations.
114
///
115
/// The **lux** (symbol: **lx**) is the unit of [illuminance], or [luminous flux] per unit area,
116
/// in the [International System of Units] (SI). It is equal to one lumen per square meter.
117
///
118
/// For more information, see [wikipedia](https://en.wikipedia.org/wiki/Lux)
119
///
120
/// [illuminance]: https://en.wikipedia.org/wiki/Illuminance
121
/// [luminous flux]: https://en.wikipedia.org/wiki/Luminous_flux
122
/// [International System of Units]: https://en.wikipedia.org/wiki/International_System_of_Units
123
pub mod lux {
124
/// The amount of light (lux) in a moonless, overcast night sky. (starlight)
125
pub const MOONLESS_NIGHT: f32 = 0.0001;
126
/// The amount of light (lux) during a full moon on a clear night.
127
pub const FULL_MOON_NIGHT: f32 = 0.05;
128
/// The amount of light (lux) during the dark limit of civil twilight under a clear sky.
129
pub const CIVIL_TWILIGHT: f32 = 3.4;
130
/// The amount of light (lux) in family living room lights.
131
pub const LIVING_ROOM: f32 = 50.;
132
/// The amount of light (lux) in an office building's hallway/toilet lighting.
133
pub const HALLWAY: f32 = 80.;
134
/// The amount of light (lux) in very dark overcast day
135
pub const DARK_OVERCAST_DAY: f32 = 100.;
136
/// The amount of light (lux) in an office.
137
pub const OFFICE: f32 = 320.;
138
/// The amount of light (lux) during sunrise or sunset on a clear day.
139
pub const CLEAR_SUNRISE: f32 = 400.;
140
/// The amount of light (lux) on an overcast day; typical TV studio lighting
141
pub const OVERCAST_DAY: f32 = 1000.;
142
/// The amount of light (lux) from ambient daylight (not direct sunlight).
143
/// This is the default for [`DirectionalLight`](crate::DirectionalLight)s in Bevy.
144
pub const AMBIENT_DAYLIGHT: f32 = 10_000.;
145
/// The amount of light (lux) in full daylight (not direct sun).
146
pub const FULL_DAYLIGHT: f32 = 20_000.;
147
/// The amount of light (lux) in direct sunlight.
148
pub const DIRECT_SUNLIGHT: f32 = 100_000.;
149
/// The amount of light (lux) of raw sunlight, not filtered by the atmosphere.
150
pub const RAW_SUNLIGHT: f32 = 130_000.;
151
}
152
}
153
154
/// Sets up all the light visibility and clustering infrastructure needed for rendering lights.
155
#[derive(Default)]
156
pub struct LightPlugin;
157
158
impl Plugin for LightPlugin {
159
fn build(&self, app: &mut App) {
160
app.init_resource::<GlobalVisibleClusterableObjects>()
161
.init_resource::<GlobalAmbientLight>()
162
.init_resource::<DirectionalLightShadowMap>()
163
.init_resource::<PointLightShadowMap>()
164
.init_asset::<ScatteringMedium>()
165
.register_required_components::<Camera3d, Clusters>()
166
.configure_sets(
167
PostUpdate,
168
SimulationLightSystems::CheckLightVisibility
169
.ambiguous_with(SimulationLightSystems::CheckLightVisibility),
170
)
171
.add_systems(Update, automatically_add_parallax_correction_components)
172
.add_systems(
173
PostUpdate,
174
(
175
validate_shadow_map_size.before(build_directional_light_cascades),
176
assign_objects_to_clusters
177
.in_set(SimulationLightSystems::AssignLightsToClusters)
178
.after(TransformSystems::Propagate)
179
.after(VisibilitySystems::CheckVisibility)
180
.after(CameraUpdateSystems),
181
update_directional_light_frusta
182
.in_set(SimulationLightSystems::UpdateLightFrusta)
183
// This must run after CheckVisibility because it relies on `ViewVisibility`
184
.after(VisibilitySystems::CheckVisibility)
185
.after(TransformSystems::Propagate)
186
.after(SimulationLightSystems::UpdateDirectionalLightCascades)
187
// We assume that no entity will be both a directional light and a spot light,
188
// so these systems will run independently of one another.
189
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
190
.ambiguous_with(update_spot_light_frusta),
191
update_point_light_frusta
192
.in_set(SimulationLightSystems::UpdateLightFrusta)
193
.after(TransformSystems::Propagate)
194
.after(SimulationLightSystems::AssignLightsToClusters),
195
#[cfg(feature = "bevy_gizmos")]
196
update_spot_light_frusta
197
.in_set(SimulationLightSystems::UpdateLightFrusta)
198
.before(FrustumGizmoSystems)
199
.after(TransformSystems::Propagate)
200
.after(SimulationLightSystems::AssignLightsToClusters),
201
#[cfg(not(feature = "bevy_gizmos"))]
202
update_spot_light_frusta
203
.in_set(SimulationLightSystems::UpdateLightFrusta)
204
.after(TransformSystems::Propagate)
205
.after(SimulationLightSystems::AssignLightsToClusters),
206
(
207
check_dir_light_mesh_visibility,
208
check_point_light_mesh_visibility,
209
)
210
.in_set(SimulationLightSystems::CheckLightVisibility)
211
.after(VisibilitySystems::CalculateBounds)
212
.after(TransformSystems::Propagate)
213
.after(SimulationLightSystems::UpdateLightFrusta)
214
// Lights can "see" entities and mark them as visible. This is done to
215
// correctly render shadows for entities that are not in view of a camera,
216
// but must be renderable to cast shadows. Because of this, we need to check
217
// entity visibility and mark as visible before they can be hidden.
218
.after(VisibilitySystems::CheckVisibility)
219
.before(VisibilitySystems::MarkNewlyHiddenEntitiesInvisible),
220
build_directional_light_cascades
221
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
222
.after(TransformSystems::Propagate)
223
.after(CameraUpdateSystems),
224
),
225
);
226
227
#[cfg(feature = "bevy_gizmos")]
228
app.add_plugins(gizmos::LightGizmoPlugin);
229
}
230
}
231
232
/// A convenient alias for `Or<(With<PointLight>, With<SpotLight>,
233
/// With<DirectionalLight>)>`, for use with [`bevy_camera::visibility::VisibleEntities`].
234
pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>;
235
236
/// Add this component to make a [`Mesh3d`] not cast shadows.
237
#[derive(Debug, Component, Reflect, Default, Clone, PartialEq)]
238
#[reflect(Component, Default, Debug, Clone, PartialEq)]
239
pub struct NotShadowCaster;
240
/// Add this component to make a [`Mesh3d`] not receive shadows.
241
///
242
/// **Note:** If you're using diffuse transmission, setting [`NotShadowReceiver`] will
243
/// cause both “regular” shadows as well as diffusely transmitted shadows to be disabled,
244
/// even when [`TransmittedShadowReceiver`] is being used.
245
#[derive(Debug, Component, Reflect, Default)]
246
#[reflect(Component, Default, Debug)]
247
pub struct NotShadowReceiver;
248
/// Add this component to make a [`Mesh3d`] using a PBR material with `StandardMaterial::diffuse_transmission > 0.0`
249
/// receive shadows on its diffuse transmission lobe. (i.e. its “backside”)
250
///
251
/// Not enabled by default, as it requires carefully setting up `StandardMaterial::thickness`
252
/// (and potentially even baking a thickness texture!) to match the geometry of the mesh, in order to avoid self-shadow artifacts.
253
///
254
/// **Note:** Using [`NotShadowReceiver`] overrides this component.
255
#[derive(Debug, Component, Reflect, Default)]
256
#[reflect(Component, Default, Debug)]
257
pub struct TransmittedShadowReceiver;
258
259
/// Add this component to a [`Camera3d`]
260
/// to control how to anti-alias shadow edges.
261
///
262
/// The different modes use different approaches to
263
/// [Percentage Closer Filtering](https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-11-shadow-map-antialiasing).
264
#[derive(Debug, Component, Reflect, Clone, Copy, PartialEq, Eq, Default)]
265
#[reflect(Component, Default, Debug, PartialEq, Clone)]
266
pub enum ShadowFilteringMethod {
267
/// Hardware 2x2.
268
///
269
/// Fast but poor quality.
270
Hardware2x2,
271
/// Approximates a fixed Gaussian blur, good when TAA isn't in use.
272
///
273
/// Good quality, good performance.
274
///
275
/// For directional and spot lights, this uses a [method by Ignacio Castaño
276
/// for *The Witness*] using 9 samples and smart filtering to achieve the same
277
/// as a regular 5x5 filter kernel.
278
///
279
/// [method by Ignacio Castaño for *The Witness*]: https://web.archive.org/web/20230210095515/http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/
280
#[default]
281
Gaussian,
282
/// A randomized filter that varies over time, good when TAA is in use.
283
///
284
/// Good quality when used with `TemporalAntiAliasing`
285
/// and good performance.
286
///
287
/// For directional and spot lights, this uses a [method by Jorge Jimenez for
288
/// *Call of Duty: Advanced Warfare*] using 8 samples in spiral pattern,
289
/// randomly-rotated by interleaved gradient noise with spatial variation.
290
///
291
/// [method by Jorge Jimenez for *Call of Duty: Advanced Warfare*]: https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
292
Temporal,
293
}
294
295
/// System sets used to run light-related systems.
296
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
297
pub enum SimulationLightSystems {
298
/// After this set, all lights have been clustered.
299
AssignLightsToClusters,
300
/// After this set, all directional light cascades are up to date.
301
UpdateDirectionalLightCascades,
302
/// After this set, the frusta of shadow-casting point lights, spot lights, and directional lights are up to date.
303
UpdateLightFrusta,
304
/// System order ambiguities between systems in this set are ignored:
305
/// the order of systems within this set is irrelevant, as the various visibility-checking systems
306
/// assumes that their operations are irreversible during the frame.
307
CheckLightVisibility,
308
}
309
310
fn shrink_entities(visible_entities: &mut Vec<Entity>) {
311
// Check that visible entities capacity() is no more than two times greater than len()
312
let capacity = visible_entities.capacity();
313
let reserved = capacity
314
.checked_div(visible_entities.len())
315
.map_or(0, |reserve| {
316
if reserve > 2 {
317
capacity / (reserve / 2)
318
} else {
319
capacity
320
}
321
});
322
323
visible_entities.shrink_to(reserved);
324
}
325
326
/// Updates the visibility for [`DirectionalLight`]s so that shadow map rendering can work.
327
pub fn check_dir_light_mesh_visibility(
328
mut commands: Commands,
329
mut directional_lights: Query<
330
(
331
&DirectionalLight,
332
&CascadesFrusta,
333
&mut CascadesVisibleEntities,
334
Option<&RenderLayers>,
335
&ViewVisibility,
336
),
337
Without<SpotLight>,
338
>,
339
visible_entity_query: Query<
340
(
341
Entity,
342
&InheritedVisibility,
343
Option<&RenderLayers>,
344
Option<&Aabb>,
345
Option<&GlobalTransform>,
346
Has<VisibilityRange>,
347
Has<NoFrustumCulling>,
348
),
349
(
350
Without<NotShadowCaster>,
351
Without<DirectionalLight>,
352
With<Mesh3d>,
353
),
354
>,
355
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
356
mut defer_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
357
mut view_visible_entities_queue: Local<Parallel<Vec<Vec<Entity>>>>,
358
) {
359
let visible_entity_ranges = visible_entity_ranges.as_deref();
360
361
for (directional_light, frusta, mut visible_entities, maybe_view_mask, light_view_visibility) in
362
&mut directional_lights
363
{
364
let mut views_to_remove = Vec::new();
365
for (view, cascade_view_entities) in &mut visible_entities.entities {
366
match frusta.frusta.get(view) {
367
Some(view_frusta) => {
368
cascade_view_entities.resize(view_frusta.len(), Default::default());
369
cascade_view_entities.iter_mut().for_each(|x| x.clear());
370
}
371
None => views_to_remove.push(*view),
372
};
373
}
374
for (view, frusta) in &frusta.frusta {
375
visible_entities
376
.entities
377
.entry(*view)
378
.or_insert_with(|| vec![VisibleMeshEntities::default(); frusta.len()]);
379
}
380
381
for v in views_to_remove {
382
visible_entities.entities.remove(&v);
383
}
384
385
// NOTE: If shadow mapping is disabled for the light then it must have no visible entities
386
if !directional_light.shadow_maps_enabled || !light_view_visibility.get() {
387
continue;
388
}
389
390
let view_mask = maybe_view_mask.unwrap_or_default();
391
392
for (view, view_frusta) in &frusta.frusta {
393
visible_entity_query.par_iter().for_each_init(
394
|| {
395
let mut entities = view_visible_entities_queue.borrow_local_mut();
396
entities.resize(view_frusta.len(), Vec::default());
397
(defer_visible_entities_queue.borrow_local_mut(), entities)
398
},
399
|(defer_visible_entities_local_queue, view_visible_entities_local_queue),
400
(
401
entity,
402
inherited_visibility,
403
maybe_entity_mask,
404
maybe_aabb,
405
maybe_transform,
406
has_visibility_range,
407
has_no_frustum_culling,
408
)| {
409
if !inherited_visibility.get() {
410
return;
411
}
412
413
let entity_mask = maybe_entity_mask.unwrap_or_default();
414
if !view_mask.intersects(entity_mask) {
415
return;
416
}
417
418
// Check visibility ranges.
419
if has_visibility_range
420
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
421
!visible_entity_ranges.entity_is_in_range_of_view(entity, *view)
422
})
423
{
424
return;
425
}
426
427
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
428
let mut visible = false;
429
for (frustum, frustum_visible_entities) in view_frusta
430
.iter()
431
.zip(view_visible_entities_local_queue.iter_mut())
432
{
433
// Disable near-plane culling, as a shadow caster could lie before the near plane.
434
if !has_no_frustum_culling
435
&& !frustum.intersects_obb(aabb, &transform.affine(), false, true)
436
{
437
continue;
438
}
439
visible = true;
440
441
frustum_visible_entities.push(entity);
442
}
443
if visible {
444
defer_visible_entities_local_queue.push(entity);
445
}
446
} else {
447
defer_visible_entities_local_queue.push(entity);
448
for frustum_visible_entities in view_visible_entities_local_queue.iter_mut()
449
{
450
frustum_visible_entities.push(entity);
451
}
452
}
453
},
454
);
455
// collect entities from parallel queue
456
for entities in view_visible_entities_queue.iter_mut() {
457
visible_entities
458
.entities
459
.get_mut(view)
460
.unwrap()
461
.iter_mut()
462
.zip(entities.iter_mut())
463
.for_each(|(dst, source)| {
464
dst.append(source);
465
});
466
}
467
}
468
469
for (_, cascade_view_entities) in &mut visible_entities.entities {
470
cascade_view_entities
471
.iter_mut()
472
.map(DerefMut::deref_mut)
473
.for_each(shrink_entities);
474
}
475
}
476
477
// Defer marking view visibility so this system can run in parallel with check_point_light_mesh_visibility
478
// TODO: use resource to avoid unnecessary memory alloc
479
let mut defer_queue = core::mem::take(defer_visible_entities_queue.deref_mut());
480
commands.queue(move |world: &mut World| {
481
let mut query = world.query::<&mut ViewVisibility>();
482
for entities in defer_queue.iter_mut() {
483
let mut iter = query.iter_many_mut(world, entities.iter());
484
while let Some(mut view_visibility) = iter.fetch_next() {
485
view_visibility.set_visible();
486
}
487
}
488
});
489
}
490
491
/// Updates the visibility for [`PointLight`]s and [`SpotLight`]s so that
492
/// shadow map rendering can work.
493
pub fn check_point_light_mesh_visibility(
494
visible_point_lights: Query<&VisibleClusterableObjects>,
495
mut point_lights: Query<(
496
&PointLight,
497
&GlobalTransform,
498
&CubemapFrusta,
499
&mut CubemapVisibleEntities,
500
Option<&RenderLayers>,
501
)>,
502
mut spot_lights: Query<(
503
&SpotLight,
504
&GlobalTransform,
505
&Frustum,
506
&mut VisibleMeshEntities,
507
Option<&RenderLayers>,
508
)>,
509
mut visible_entity_query: Query<
510
(
511
Entity,
512
&InheritedVisibility,
513
&mut ViewVisibility,
514
Option<&RenderLayers>,
515
Option<&Aabb>,
516
Option<&GlobalTransform>,
517
Has<VisibilityRange>,
518
Has<NoFrustumCulling>,
519
),
520
(
521
Without<NotShadowCaster>,
522
Without<DirectionalLight>,
523
With<Mesh3d>,
524
),
525
>,
526
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
527
mut cubemap_visible_entities_queue: Local<Parallel<[Vec<Entity>; 6]>>,
528
mut spot_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
529
mut checked_lights: Local<EntityHashSet>,
530
) {
531
checked_lights.clear();
532
533
let visible_entity_ranges = visible_entity_ranges.as_deref();
534
for visible_lights in &visible_point_lights {
535
for light_entity in visible_lights.point_and_spot_lights.iter().copied() {
536
if !checked_lights.insert(light_entity) {
537
continue;
538
}
539
540
// Point lights
541
if let Ok((
542
point_light,
543
transform,
544
cubemap_frusta,
545
mut cubemap_visible_entities,
546
maybe_view_mask,
547
)) = point_lights.get_mut(light_entity)
548
{
549
for visible_entities in cubemap_visible_entities.iter_mut() {
550
visible_entities.entities.clear();
551
}
552
553
// NOTE: If shadow mapping is disabled for the light then it must have no visible entities
554
if !point_light.shadow_maps_enabled {
555
continue;
556
}
557
558
let view_mask = maybe_view_mask.unwrap_or_default();
559
let light_sphere = Sphere {
560
center: Vec3A::from(transform.translation()),
561
radius: point_light.range,
562
};
563
564
visible_entity_query.par_iter_mut().for_each_init(
565
|| cubemap_visible_entities_queue.borrow_local_mut(),
566
|cubemap_visible_entities_local_queue,
567
(
568
entity,
569
inherited_visibility,
570
mut view_visibility,
571
maybe_entity_mask,
572
maybe_aabb,
573
maybe_transform,
574
has_visibility_range,
575
has_no_frustum_culling,
576
)| {
577
if !inherited_visibility.get() {
578
return;
579
}
580
let entity_mask = maybe_entity_mask.unwrap_or_default();
581
if !view_mask.intersects(entity_mask) {
582
return;
583
}
584
if has_visibility_range
585
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
586
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
587
})
588
{
589
return;
590
}
591
592
// If we have an aabb and transform, do frustum culling
593
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
594
let model_to_world = transform.affine();
595
// Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light
596
if !has_no_frustum_culling
597
&& !light_sphere.intersects_obb(aabb, &model_to_world)
598
{
599
return;
600
}
601
602
for (frustum, visible_entities) in cubemap_frusta
603
.iter()
604
.zip(cubemap_visible_entities_local_queue.iter_mut())
605
{
606
if has_no_frustum_culling
607
|| frustum.intersects_obb(aabb, &model_to_world, true, true)
608
{
609
view_visibility.set_visible();
610
visible_entities.push(entity);
611
}
612
}
613
} else {
614
view_visibility.set_visible();
615
for visible_entities in cubemap_visible_entities_local_queue.iter_mut()
616
{
617
visible_entities.push(entity);
618
}
619
}
620
},
621
);
622
623
for entities in cubemap_visible_entities_queue.iter_mut() {
624
for (dst, source) in
625
cubemap_visible_entities.iter_mut().zip(entities.iter_mut())
626
{
627
dst.entities.append(source);
628
}
629
}
630
631
for visible_entities in cubemap_visible_entities.iter_mut() {
632
shrink_entities(visible_entities);
633
}
634
}
635
636
// Spot lights
637
if let Ok((point_light, transform, frustum, mut visible_entities, maybe_view_mask)) =
638
spot_lights.get_mut(light_entity)
639
{
640
visible_entities.clear();
641
642
// NOTE: If shadow mapping is disabled for the light then it must have no visible entities
643
if !point_light.shadow_maps_enabled {
644
continue;
645
}
646
647
let view_mask = maybe_view_mask.unwrap_or_default();
648
let light_sphere = Sphere {
649
center: Vec3A::from(transform.translation()),
650
radius: point_light.range,
651
};
652
653
visible_entity_query.par_iter_mut().for_each_init(
654
|| spot_visible_entities_queue.borrow_local_mut(),
655
|spot_visible_entities_local_queue,
656
(
657
entity,
658
inherited_visibility,
659
mut view_visibility,
660
maybe_entity_mask,
661
maybe_aabb,
662
maybe_transform,
663
has_visibility_range,
664
has_no_frustum_culling,
665
)| {
666
if !inherited_visibility.get() {
667
return;
668
}
669
670
let entity_mask = maybe_entity_mask.unwrap_or_default();
671
if !view_mask.intersects(entity_mask) {
672
return;
673
}
674
// Check visibility ranges.
675
if has_visibility_range
676
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
677
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
678
})
679
{
680
return;
681
}
682
683
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
684
let model_to_world = transform.affine();
685
// Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light
686
if !has_no_frustum_culling
687
&& !light_sphere.intersects_obb(aabb, &model_to_world)
688
{
689
return;
690
}
691
692
if has_no_frustum_culling
693
|| frustum.intersects_obb(aabb, &model_to_world, true, true)
694
{
695
view_visibility.set_visible();
696
spot_visible_entities_local_queue.push(entity);
697
}
698
} else {
699
view_visibility.set_visible();
700
spot_visible_entities_local_queue.push(entity);
701
}
702
},
703
);
704
705
for entities in spot_visible_entities_queue.iter_mut() {
706
visible_entities.append(entities);
707
}
708
709
shrink_entities(visible_entities.deref_mut());
710
}
711
}
712
}
713
}
714
715