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