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