1use crate::*;
2use alloc::sync::Arc;
3use bevy_asset::UntypedAssetId;
4use bevy_camera::primitives::{
5 face_index_to_name, CascadesFrusta, CubeMapFace, CubemapFrusta, Frustum, CUBE_MAP_FACES,
6};
7use bevy_camera::visibility::{
8 CascadesVisibleEntities, CubemapVisibleEntities, RenderLayers, ViewVisibility,
9 VisibleMeshEntities,
10};
11use bevy_camera::{Camera, Camera3d, RenderTarget, ShadowLodOrigin};
12use bevy_color::ColorToComponents;
13use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
14use bevy_core_pipeline::schedule::RootNonCameraView;
15use bevy_derive::{Deref, DerefMut};
16use bevy_ecs::schedule::ScheduleLabel;
17use bevy_ecs::{
18 entity::{EntityHashMap, EntityHashSet},
19 prelude::*,
20 system::{SystemParam, SystemState},
21};
22use bevy_light::cascade::Cascade;
23use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType};
24use bevy_light::SunDisk;
25use bevy_light::{
26 spot_light_clip_from_view, spot_light_world_from_view, AmbientLight, CascadeShadowConfig,
27 Cascades, DirectionalLight, DirectionalLightShadowMap, GlobalAmbientLight, PointLight,
28 PointLightShadowMap, RectLight, ShadowFilteringMethod, SpotLight, VolumetricLight,
29};
30use bevy_material::{
31 key::{ErasedMaterialPipelineKey, ErasedMeshPipelineKey},
32 MaterialProperties,
33};
34use bevy_math::{
35 ops,
36 primitives::{HalfSpace, ViewFrustum},
37 Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles,
38};
39use bevy_mesh::{Mesh3d, MeshVertexBufferLayoutRef};
40use bevy_platform::collections::{HashMap, HashSet};
41use bevy_platform::hash::FixedHasher;
42use bevy_render::camera::{DirtySpecializations, PendingQueues};
43use bevy_render::erased_render_asset::ErasedRenderAssets;
44use bevy_render::mesh::allocator::MeshSlabs;
45use bevy_render::occlusion_culling::{
46 OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
47};
48use bevy_render::sync_world::{MainEntity, MainEntityHashMap, RenderEntity};
49use bevy_render::view::{
50 RenderExtractedShadowMapVisibleEntities, RenderShadowLodOrigin, RenderShadowMapVisibleEntities,
51 RenderVisibleEntities, VisibilityExtractionSystemParam,
52};
53use bevy_render::{
54 batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
55 camera::SortedCameras,
56 mesh::allocator::MeshAllocator,
57 view::{NoIndirectDrawing, RetainedViewEntity},
58};
59use bevy_render::{
60 mesh::RenderMesh,
61 render_asset::RenderAssets,
62 render_phase::*,
63 render_resource::*,
64 renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
65 texture::*,
66 view::ExtractedView,
67 Extract,
68};
69use bevy_transform::{components::GlobalTransform, prelude::Transform};
70use bevy_utils::default;
71use core::{any::TypeId, hash::Hash, mem, ops::Range};
72use decal::clustered::RenderClusteredDecals;
73#[cfg(feature = "trace")]
74use tracing::info_span;
75use tracing::{error, warn};
76
77#[derive(Component)]
78#[require(PointAndSpotLightViewEntities)]
79pub struct ExtractedPointLight {
80 pub color: LinearRgba,
81 pub intensity: f32,
83 pub range: f32,
84 pub radius: f32,
85 pub transform: GlobalTransform,
86 pub shadow_maps_enabled: bool,
87 pub contact_shadows_enabled: bool,
88 pub shadow_depth_bias: f32,
89 pub shadow_normal_bias: f32,
90 pub shadow_map_near_z: f32,
91 pub spot_light_angles: Option<(f32, f32)>,
92 pub volumetric: bool,
93 pub soft_shadows_enabled: bool,
94 pub affects_lightmapped_mesh_diffuse: bool,
96}
97
98#[derive(Component, Debug)]
99pub struct ExtractedRectLight {
100 pub color: LinearRgba,
101 pub intensity: f32,
102 pub range: f32,
103 pub width: f32,
104 pub height: f32,
105 pub transform: GlobalTransform,
106}
107
108#[derive(Component, Debug)]
109pub struct ExtractedDirectionalLight {
110 pub color: LinearRgba,
111 pub illuminance: f32,
112 pub transform: GlobalTransform,
113 pub shadow_maps_enabled: bool,
114 pub contact_shadows_enabled: bool,
115 pub volumetric: bool,
116 pub affects_lightmapped_mesh_diffuse: bool,
119 pub shadow_depth_bias: f32,
120 pub shadow_normal_bias: f32,
121 pub cascade_shadow_config: CascadeShadowConfig,
122 pub cascades: EntityHashMap<Vec<Cascade>>,
123 pub frusta: EntityHashMap<Vec<Frustum>>,
124 pub render_layers: RenderLayers,
125 pub soft_shadow_size: Option<f32>,
126 pub occlusion_culling: bool,
128 pub sun_disk_angular_size: f32,
129 pub sun_disk_intensity: f32,
130}
131
132bitflags::bitflags! {
134 #[repr(transparent)]
135 struct PointLightFlags: u32 {
136 const SHADOW_MAPS_ENABLED = 1 << 0;
137 const SPOT_LIGHT_Y_NEGATIVE = 1 << 1;
138 const VOLUMETRIC = 1 << 2;
139 const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1 << 3;
140 const CONTACT_SHADOWS_ENABLED = 1 << 4;
141 const SPOT_LIGHT = 1 << 5;
142 const NONE = 0;
143 const UNINITIALIZED = 0xFFFF;
144 }
145}
146
147#[derive(Copy, Clone, ShaderType, Default, Debug)]
148pub struct GpuDirectionalCascade {
149 clip_from_world: Mat4,
150 texel_size: f32,
151 far_bound: f32,
152}
153
154#[derive(Copy, Clone, ShaderType, Default, Debug)]
155pub struct GpuDirectionalLight {
156 cascades: [GpuDirectionalCascade; MAX_CASCADES_PER_LIGHT],
157 color: Vec4,
158 dir_to_light: Vec3,
159 flags: u32,
160 soft_shadow_size: f32,
161 shadow_depth_bias: f32,
162 shadow_normal_bias: f32,
163 num_cascades: u32,
164 cascades_overlap_proportion: f32,
165 depth_texture_base_index: u32,
166 decal_index: u32,
167 sun_disk_angular_size: f32,
168 sun_disk_intensity: f32,
169}
170
171bitflags::bitflags! {
173 #[repr(transparent)]
174 struct DirectionalLightFlags: u32 {
175 const SHADOW_MAPS_ENABLED = 1 << 0;
176 const VOLUMETRIC = 1 << 1;
177 const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1 << 2;
178 const CONTACT_SHADOWS_ENABLED = 1 << 3;
179 const NONE = 0;
180 const UNINITIALIZED = 0xFFFF;
181 }
182}
183
184#[derive(Copy, Clone, ShaderType, Default, Debug)]
185pub struct GpuRectLight {
186 color: Vec4,
187 position: Vec3,
188 width: f32,
189 right: Vec3,
190 height: f32,
191 up: Vec3,
192 range: f32,
193}
194
195#[derive(Copy, Clone, Debug, ShaderType)]
196pub struct GpuLights {
197 directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
198 ambient_color: Vec4,
199 cluster_dimensions: UVec4,
201 cluster_factors: Vec4,
205 n_directional_lights: u32,
206 spot_light_shadowmap_offset: i32,
208 ambient_light_affects_lightmapped_meshes: u32,
209 n_rect_lights: u32,
210 rect_lights: [GpuRectLight; MAX_RECT_LIGHTS],
211}
212
213#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
216pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
217#[cfg(any(
218 not(feature = "webgl"),
219 not(target_arch = "wasm32"),
220 feature = "webgpu"
221))]
222pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
223#[cfg(any(
224 not(feature = "webgl"),
225 not(target_arch = "wasm32"),
226 feature = "webgpu"
227))]
228pub const MAX_CASCADES_PER_LIGHT: usize = 4;
229#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
230pub const MAX_CASCADES_PER_LIGHT: usize = 1;
231
232pub const MAX_RECT_LIGHTS: usize = 8;
233
234#[derive(Resource, Clone)]
235pub struct ShadowSamplers {
236 pub point_light_comparison_sampler: Sampler,
237 #[cfg(feature = "experimental_pbr_pcss")]
238 pub point_light_linear_sampler: Sampler,
239 pub directional_light_comparison_sampler: Sampler,
240 #[cfg(feature = "experimental_pbr_pcss")]
241 pub directional_light_linear_sampler: Sampler,
242}
243
244pub fn init_shadow_samplers(mut commands: Commands, render_device: Res<RenderDevice>) {
245 let base_sampler_descriptor = SamplerDescriptor {
246 address_mode_u: AddressMode::ClampToEdge,
247 address_mode_v: AddressMode::ClampToEdge,
248 address_mode_w: AddressMode::ClampToEdge,
249 mag_filter: FilterMode::Linear,
250 min_filter: FilterMode::Linear,
251 mipmap_filter: MipmapFilterMode::Nearest,
252 ..default()
253 };
254
255 commands.insert_resource(ShadowSamplers {
256 point_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
257 compare: Some(CompareFunction::GreaterEqual),
258 ..base_sampler_descriptor
259 }),
260 #[cfg(feature = "experimental_pbr_pcss")]
261 point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
262 directional_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
263 compare: Some(CompareFunction::GreaterEqual),
264 ..base_sampler_descriptor
265 }),
266 #[cfg(feature = "experimental_pbr_pcss")]
267 directional_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
268 });
269}
270
271pub fn extract_shadow_filtering_method(
274 mut commands: Commands,
275 mut previous_len: Local<usize>,
276 query: Extract<Query<(RenderEntity, &ShadowFilteringMethod)>>,
277) {
278 let mut values = Vec::with_capacity(*previous_len);
279 for (entity, query_item) in &query {
280 values.push((entity, *query_item));
281 }
282 *previous_len = values.len();
283 commands.try_insert_batch(values);
284}
285
286pub fn extract_ambient_light_resource(
289 mut commands: Commands,
290 main_resource: Extract<Option<Res<GlobalAmbientLight>>>,
291 target_resource: Option<ResMut<GlobalAmbientLight>>,
292) {
293 if let Some(main_resource) = main_resource.as_ref() {
294 if let Some(mut target_resource) = target_resource {
295 if main_resource.is_changed() {
296 *target_resource = (*main_resource).clone();
297 }
298 } else {
299 commands.insert_resource((*main_resource).clone());
300 }
301 }
302}
303
304pub fn extract_ambient_light(
307 mut commands: Commands,
308 mut previous_len: Local<usize>,
309 query: Extract<Query<(RenderEntity, &AmbientLight)>>,
310) {
311 let mut values = Vec::with_capacity(*previous_len);
312 for (entity, query_item) in &query {
313 values.push((entity, query_item.clone()));
314 }
315 *previous_len = values.len();
316 commands.try_insert_batch(values);
317}
318
319pub fn extract_lights(
320 mut commands: Commands,
321 point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
322 directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
323 point_lights: Extract<
324 Query<
325 (
326 Entity,
327 RenderEntity,
328 &PointLight,
329 &CubemapVisibleEntities,
330 &GlobalTransform,
331 &ViewVisibility,
332 &CubemapFrusta,
333 Option<&VolumetricLight>,
334 ),
335 Or<(
336 Changed<PointLight>,
337 Changed<CubemapVisibleEntities>,
338 Changed<GlobalTransform>,
339 Changed<ViewVisibility>,
340 Changed<CubemapFrusta>,
341 Changed<VolumetricLight>,
342 )>,
343 >,
344 >,
345 spot_lights: Extract<
346 Query<
347 (
348 Entity,
349 RenderEntity,
350 &SpotLight,
351 &VisibleMeshEntities,
352 &GlobalTransform,
353 &ViewVisibility,
354 &Frustum,
355 Option<&VolumetricLight>,
356 ),
357 Or<(
358 Changed<SpotLight>,
359 Changed<VisibleMeshEntities>,
360 Changed<GlobalTransform>,
361 Changed<ViewVisibility>,
362 Changed<Frustum>,
363 Changed<VolumetricLight>,
364 )>,
365 >,
366 >,
367 directional_lights: Extract<
368 Query<
369 (
370 Entity,
371 RenderEntity,
372 &DirectionalLight,
373 &CascadesVisibleEntities,
374 &Cascades,
375 &CascadeShadowConfig,
376 &CascadesFrusta,
377 &GlobalTransform,
378 &ViewVisibility,
379 Option<&RenderLayers>,
380 Option<&VolumetricLight>,
381 Has<OcclusionCulling>,
382 Option<&SunDisk>,
383 ),
384 (
385 Without<SpotLight>,
386 Or<(
387 Changed<DirectionalLight>,
388 Changed<CascadesVisibleEntities>,
389 Changed<Cascades>,
390 Changed<CascadeShadowConfig>,
391 Changed<CascadesFrusta>,
392 Changed<GlobalTransform>,
393 Changed<ViewVisibility>,
394 Changed<RenderLayers>,
395 Changed<VolumetricLight>,
396 Changed<OcclusionCulling>,
397 Changed<SunDisk>,
398 )>,
399 ),
400 >,
401 >,
402 rect_lights: Extract<
403 Query<
404 (
405 Entity,
406 RenderEntity,
407 &RectLight,
408 &GlobalTransform,
409 &ViewVisibility,
410 ),
411 Or<(
412 Changed<RectLight>,
413 Changed<GlobalTransform>,
414 Changed<ViewVisibility>,
415 )>,
416 >,
417 >,
418 visibility_extraction_system_param: VisibilityExtractionSystemParam,
419 mut existing_render_shadow_map_visible_entities: Query<(
420 &mut RenderExtractedShadowMapVisibleEntities,
421 &mut RenderShadowMapVisibleEntities,
422 )>,
423 mut rect_light_missing_luts_warning_emitted: Local<bool>,
424) {
425 let mapper = &visibility_extraction_system_param.mapper;
426
427 if point_light_shadow_map.is_changed() {
430 commands.insert_resource(point_light_shadow_map.clone());
431 }
432 if directional_light_shadow_map.is_changed() {
433 commands.insert_resource(directional_light_shadow_map.clone());
434 }
435
436 let point_light_texel_size = 2.0 / point_light_shadow_map.size as f32;
444
445 for (
446 main_entity,
447 render_entity,
448 point_light,
449 cubemap_visible_entities,
450 transform,
451 view_visibility,
452 frusta,
453 volumetric_light,
454 ) in point_lights.iter()
455 {
456 if !view_visibility.get() {
457 if let Ok(mut entity_commands) = commands.get_entity(render_entity) {
458 entity_commands.remove::<ExtractedPointLight>();
459 }
460 continue;
461 }
462
463 if !point_light.shadow_maps_enabled {
464 clear_shadow_maps(&mut commands, render_entity);
465 } else {
466 let (
468 mut render_extracted_shadow_map_visible_entities,
469 mut render_shadow_map_visible_entities,
470 ) = match existing_render_shadow_map_visible_entities.get_mut(render_entity) {
471 Ok((
472 ref mut existing_extracted_shadow_map_visible_entities,
473 ref mut existing_shadow_map_visible_entities,
474 )) => (
475 mem::take(&mut **existing_extracted_shadow_map_visible_entities),
476 mem::take(&mut **existing_shadow_map_visible_entities),
477 ),
478 Err(_) => (
479 RenderExtractedShadowMapVisibleEntities::default(),
480 RenderShadowMapVisibleEntities::default(),
481 ),
482 };
483
484 for face_index in 0..6 {
485 let retained_view_entity = RetainedViewEntity {
486 main_entity: MainEntity::from(main_entity),
487 auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER),
488 subview_index: face_index,
489 };
490 render_shadow_map_visible_entities
491 .subviews
492 .entry(retained_view_entity)
493 .or_default();
494
495 let extracted_entities = &mut render_extracted_shadow_map_visible_entities
497 .subviews
498 .entry(retained_view_entity)
499 .or_default()
500 .classes
501 .entry(TypeId::of::<Mesh3d>())
502 .or_default()
503 .entities;
504 extracted_entities.clear();
505 let visible_mesh_entities = cubemap_visible_entities.get(face_index as usize);
506 extracted_entities.extend(visible_mesh_entities.entities.iter().map(
507 |main_entity| {
508 let render_entity = match mapper.get(*main_entity) {
509 Ok(render_entity) => **render_entity,
510 Err(_) => Entity::PLACEHOLDER,
511 };
512 (render_entity, MainEntity::from(*main_entity))
513 },
514 ));
515 }
516
517 let mut entity_commands = commands.entity(render_entity);
518 entity_commands.insert((
519 render_extracted_shadow_map_visible_entities,
520 render_shadow_map_visible_entities,
521 ));
522 }
523
524 let mut entity_commands = commands.entity(render_entity);
525 let extracted_point_light = ExtractedPointLight {
526 color: point_light.color.into(),
527 intensity: point_light.intensity / (4.0 * core::f32::consts::PI),
531 range: point_light.range,
532 radius: point_light.radius,
533 transform: *transform,
534 shadow_maps_enabled: point_light.shadow_maps_enabled,
535 contact_shadows_enabled: point_light.contact_shadows_enabled,
536 shadow_depth_bias: point_light.shadow_depth_bias,
537 shadow_normal_bias: point_light.shadow_normal_bias
539 * point_light_texel_size
540 * core::f32::consts::SQRT_2,
541 shadow_map_near_z: point_light.shadow_map_near_z,
542 spot_light_angles: None,
543 volumetric: volumetric_light.is_some(),
544 affects_lightmapped_mesh_diffuse: point_light.affects_lightmapped_mesh_diffuse,
545 #[cfg(feature = "experimental_pbr_pcss")]
546 soft_shadows_enabled: point_light.soft_shadows_enabled,
547 #[cfg(not(feature = "experimental_pbr_pcss"))]
548 soft_shadows_enabled: false,
549 };
550 entity_commands.insert((
551 extracted_point_light,
552 (*frusta).clone(),
553 MainEntity::from(main_entity),
554 ));
555 }
556
557 for (
558 main_entity,
559 render_entity,
560 spot_light,
561 visible_entities,
562 transform,
563 view_visibility,
564 frustum,
565 volumetric_light,
566 ) in spot_lights.iter()
567 {
568 if !view_visibility.get() {
569 if let Ok(mut entity_commands) = commands.get_entity(render_entity) {
570 entity_commands.remove::<ExtractedPointLight>();
571 }
572 continue;
573 }
574
575 if !spot_light.shadow_maps_enabled {
576 clear_shadow_maps(&mut commands, render_entity);
577 } else {
578 let (
580 mut render_extracted_shadow_map_visible_entities,
581 mut render_shadow_map_visible_entities,
582 ) = match existing_render_shadow_map_visible_entities.get_mut(render_entity) {
583 Ok((
584 ref mut existing_extracted_shadow_map_visible_entities,
585 ref mut existing_shadow_map_visible_entities,
586 )) => (
587 mem::take(&mut **existing_extracted_shadow_map_visible_entities),
588 mem::take(&mut **existing_shadow_map_visible_entities),
589 ),
590 Err(_) => (
591 RenderExtractedShadowMapVisibleEntities::default(),
592 RenderShadowMapVisibleEntities::default(),
593 ),
594 };
595
596 let retained_view_entity = RetainedViewEntity {
597 main_entity: MainEntity::from(main_entity),
598 auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER),
599 subview_index: 0,
600 };
601 render_shadow_map_visible_entities
602 .subviews
603 .entry(retained_view_entity)
604 .or_default();
605
606 let entities_cpu_culling = &mut render_extracted_shadow_map_visible_entities
608 .subviews
609 .entry(retained_view_entity)
610 .or_default()
611 .classes
612 .entry(TypeId::of::<Mesh3d>())
613 .or_default()
614 .entities;
615 entities_cpu_culling.clear();
616 entities_cpu_culling.extend(visible_entities.entities.iter().map(|main_entity| {
617 let render_entity = match mapper.get(*main_entity) {
618 Ok(render_entity) => **render_entity,
619 Err(_) => Entity::PLACEHOLDER,
620 };
621 (render_entity, MainEntity::from(*main_entity))
622 }));
623
624 let mut entity_commands = commands.entity(render_entity);
625 entity_commands.insert((
626 render_extracted_shadow_map_visible_entities,
627 render_shadow_map_visible_entities,
628 ));
629 }
630
631 let texel_size =
632 2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32;
633
634 let mut entity_commands = commands.entity(render_entity);
635 let extracted_spot_light = ExtractedPointLight {
636 color: spot_light.color.into(),
637 intensity: spot_light.intensity / (4.0 * core::f32::consts::PI),
644 range: spot_light.range,
645 radius: spot_light.radius,
646 transform: *transform,
647 shadow_maps_enabled: spot_light.shadow_maps_enabled,
648 contact_shadows_enabled: spot_light.contact_shadows_enabled,
649 shadow_depth_bias: spot_light.shadow_depth_bias,
650 shadow_normal_bias: spot_light.shadow_normal_bias
652 * texel_size
653 * core::f32::consts::SQRT_2,
654 shadow_map_near_z: spot_light.shadow_map_near_z,
655 spot_light_angles: Some((spot_light.inner_angle, spot_light.outer_angle)),
656 volumetric: volumetric_light.is_some(),
657 affects_lightmapped_mesh_diffuse: spot_light.affects_lightmapped_mesh_diffuse,
658 #[cfg(feature = "experimental_pbr_pcss")]
659 soft_shadows_enabled: spot_light.soft_shadows_enabled,
660 #[cfg(not(feature = "experimental_pbr_pcss"))]
661 soft_shadows_enabled: false,
662 };
663 entity_commands.insert((
664 extracted_spot_light,
665 *frustum,
666 MainEntity::from(main_entity),
667 ));
668 }
669
670 for (
671 main_entity,
672 entity,
673 directional_light,
674 visible_entities,
675 cascades,
676 cascade_config,
677 frusta,
678 transform,
679 view_visibility,
680 maybe_layers,
681 volumetric_light,
682 occlusion_culling,
683 sun_disk,
684 ) in &directional_lights
685 {
686 if !view_visibility.get() {
687 commands
688 .get_entity(entity)
689 .expect("Light entity wasn't synced.")
690 .remove::<(
691 ExtractedDirectionalLight,
692 RenderExtractedShadowMapVisibleEntities,
693 )>();
694 continue;
695 }
696
697 let mut extracted_cascades = EntityHashMap::default();
699 let mut extracted_frusta = EntityHashMap::default();
700
701 if !directional_light.shadow_maps_enabled {
702 clear_shadow_maps(&mut commands, entity);
703 } else {
704 let (
706 mut existing_extracted_shadow_map_visible_entities,
707 mut existing_shadow_map_visible_entities,
708 ) = match existing_render_shadow_map_visible_entities.get_mut(entity) {
709 Ok((
710 ref mut existing_extracted_shadow_map_visible_entities,
711 ref mut existing_shadow_map_visible_entities,
712 )) => (
713 mem::take(&mut **existing_extracted_shadow_map_visible_entities),
714 mem::take(&mut **existing_shadow_map_visible_entities),
715 ),
716 Err(_) => (
717 RenderExtractedShadowMapVisibleEntities::default(),
718 RenderShadowMapVisibleEntities::default(),
719 ),
720 };
721
722 for (e, v) in cascades.cascades.iter() {
723 if let Ok(entity) = mapper.get(*e) {
724 extracted_cascades.insert(**entity, v.clone());
725 } else {
726 break;
727 }
728 }
729 for (e, v) in frusta.frusta.iter() {
730 if let Ok(entity) = mapper.get(*e) {
731 extracted_frusta.insert(**entity, v.clone());
732 } else {
733 break;
734 }
735 }
736 let mut all_cascades_seen = HashSet::new();
738 for (main_auxiliary_entity, visible_mesh_entities_list) in
739 visible_entities.entities.iter()
740 {
741 for subview_index in 0..(cascade_config.bounds.len() as u32) {
742 let retained_view_entity = RetainedViewEntity {
743 main_entity: MainEntity::from(main_entity),
744 auxiliary_entity: MainEntity::from(*main_auxiliary_entity),
745 subview_index,
746 };
747 all_cascades_seen.insert(retained_view_entity);
748
749 existing_shadow_map_visible_entities
750 .subviews
751 .entry(retained_view_entity)
752 .or_default();
753
754 let extracted_entities = &mut existing_extracted_shadow_map_visible_entities
756 .subviews
757 .entry(retained_view_entity)
758 .or_default()
759 .classes
760 .entry(TypeId::of::<Mesh3d>())
761 .or_default()
762 .entities;
763 extracted_entities.clear();
764 let Some(visible_mesh_entities) =
765 visible_mesh_entities_list.get(subview_index as usize)
766 else {
767 continue;
768 };
769 extracted_entities.extend(visible_mesh_entities.entities.iter().map(
770 |main_entity| {
771 let render_entity = match mapper.get(*main_entity) {
772 Ok(render_entity) => **render_entity,
773 Err(_) => Entity::PLACEHOLDER,
774 };
775 (render_entity, MainEntity::from(*main_entity))
776 },
777 ));
778 }
779 }
780
781 existing_extracted_shadow_map_visible_entities
784 .subviews
785 .retain(|cascade_entity, _| all_cascades_seen.contains(cascade_entity));
786 existing_shadow_map_visible_entities
787 .subviews
788 .retain(|cascade_entity, _| all_cascades_seen.contains(cascade_entity));
789
790 let mut entity_commands = commands.entity(entity);
791 entity_commands.insert((
792 existing_extracted_shadow_map_visible_entities,
793 existing_shadow_map_visible_entities,
794 ));
795 }
796
797 let extracted_directional_light = ExtractedDirectionalLight {
798 color: directional_light.color.into(),
799 illuminance: directional_light.illuminance,
800 transform: *transform,
801 volumetric: volumetric_light.is_some(),
802 affects_lightmapped_mesh_diffuse: directional_light.affects_lightmapped_mesh_diffuse,
803 #[cfg(feature = "experimental_pbr_pcss")]
804 soft_shadow_size: directional_light.soft_shadow_size,
805 #[cfg(not(feature = "experimental_pbr_pcss"))]
806 soft_shadow_size: None,
807 shadow_maps_enabled: directional_light.shadow_maps_enabled,
808 contact_shadows_enabled: directional_light.contact_shadows_enabled,
809 shadow_depth_bias: directional_light.shadow_depth_bias,
810 shadow_normal_bias: directional_light.shadow_normal_bias * core::f32::consts::SQRT_2,
812 cascade_shadow_config: cascade_config.clone(),
813 cascades: extracted_cascades,
814 frusta: extracted_frusta,
815 render_layers: maybe_layers.unwrap_or_default().clone(),
816 occlusion_culling,
817 sun_disk_angular_size: sun_disk.unwrap_or_default().angular_size,
818 sun_disk_intensity: sun_disk.unwrap_or_default().intensity,
819 };
820
821 let mut entity_commands = commands
822 .get_entity(entity)
823 .expect("Light entity wasn't synced.");
824 entity_commands.insert((extracted_directional_light, MainEntity::from(main_entity)));
825 }
826
827 for (main_entity, render_entity, rect_light, transform, view_visibility) in &rect_lights {
828 if !cfg!(feature = "area_light_luts") && !*rect_light_missing_luts_warning_emitted {
829 warn!(
830 "RectLight will not work properly because the `area_light_luts` cargo feature is not enabled."
831 );
832 *rect_light_missing_luts_warning_emitted = true;
833 }
834 if !view_visibility.get() {
835 if let Ok(mut entity_commands) = commands.get_entity(render_entity) {
836 entity_commands.remove::<ExtractedRectLight>();
837 }
838 continue;
839 }
840
841 let affine = transform.affine();
842 let effective_width = rect_light.width * affine.matrix3.x_axis.length();
843 let effective_height = rect_light.height * affine.matrix3.y_axis.length();
844 commands
845 .get_entity(render_entity)
846 .expect("RectLight entity wasn't synced.")
847 .insert((
848 ExtractedRectLight {
849 color: rect_light.color.into(),
850 intensity: rect_light.intensity
851 / (effective_width * effective_height * core::f32::consts::PI),
852 width: effective_width,
853 height: effective_height,
854 range: rect_light.range,
855 transform: *transform,
856 },
857 MainEntity::from(main_entity),
858 ));
859 }
860
861 fn clear_shadow_maps(commands: &mut Commands, render_entity: Entity) {
864 let Ok(mut entity_commands) = commands.get_entity(render_entity) else {
865 return;
866 };
867 entity_commands.remove::<(
868 RenderExtractedShadowMapVisibleEntities,
869 RenderShadowMapVisibleEntities,
870 )>();
871 }
872}
873
874#[derive(Component, Default, Deref, DerefMut)]
875pub struct DirectionalLightViewEntities(EntityHashMap<Vec<Entity>>);
878
879pub(crate) fn add_light_view_entities(
881 add: On<Add, ExtractedDirectionalLight>,
882 mut commands: Commands,
883) {
884 if let Ok(mut v) = commands.get_entity(add.entity) {
885 v.insert(DirectionalLightViewEntities::default());
886 }
887}
888
889pub(crate) fn remove_light_view_entities(
890 remove: On<Remove, DirectionalLightViewEntities>,
891 query: Query<&DirectionalLightViewEntities>,
892 mut commands: Commands,
893) {
894 if let Ok(entities) = query.get(remove.entity) {
895 for v in entities.0.values() {
896 for e in v.iter().copied() {
897 if let Ok(mut v) = commands.get_entity(e) {
898 v.despawn();
899 }
900 }
901 }
902 }
903}
904
905pub(crate) fn remove_point_and_spot_light_view_entities(
906 remove: On<Remove, PointAndSpotLightViewEntities>,
907 query: Query<&PointAndSpotLightViewEntities>,
908 mut commands: Commands,
909) {
910 if let Ok(entities) = query.get(remove.entity) {
911 for e in entities.0.iter().copied() {
912 if let Ok(mut v) = commands.get_entity(e) {
913 v.despawn();
914 }
915 }
916 }
917}
918
919#[derive(Component, Default, Deref, DerefMut)]
925pub struct PointAndSpotLightViewEntities(Vec<Entity>);
926
927#[derive(Component)]
928pub struct ShadowView {
929 pub depth_attachment: DepthAttachment,
930 pub pass_name: String,
931}
932
933#[derive(Component)]
934pub struct ViewShadowBindings {
935 pub point_light_depth_texture: Texture,
936 pub point_light_depth_texture_view: TextureView,
937 pub directional_light_depth_texture: Texture,
938 pub directional_light_depth_texture_view: TextureView,
939}
940
941#[derive(Component)]
947pub struct ViewLightEntities {
948 pub lights: Vec<Entity>,
954}
955
956#[derive(Component)]
957pub struct ViewLightsUniformOffset {
958 pub offset: u32,
959}
960
961#[derive(Resource, Default)]
962pub struct LightMeta {
963 pub view_gpu_lights: DynamicUniformBuffer<GpuLights>,
964}
965
966#[derive(Component)]
967pub enum LightEntity {
968 Directional {
969 light_entity: Entity,
970 cascade_index: usize,
971 },
972 Point {
973 light_entity: Entity,
974 face_index: usize,
975 },
976 Spot {
977 light_entity: Entity,
978 },
979}
980
981pub fn prepare_lights(
982 mut commands: Commands,
983 mut texture_cache: ResMut<TextureCache>,
984 (render_device, render_queue): (Res<RenderDevice>, Res<RenderQueue>),
985 mut global_clusterable_object_meta: ResMut<GlobalClusterableObjectMeta>,
986 mut light_meta: ResMut<LightMeta>,
987 views: Query<
988 (
989 Entity,
990 MainEntity,
991 &ExtractedView,
992 &ExtractedClusterConfig,
993 Option<&RenderLayers>,
994 Has<NoIndirectDrawing>,
995 Option<&AmbientLight>,
996 ),
997 With<Camera3d>,
998 >,
999 ambient_light: Res<GlobalAmbientLight>,
1000 point_light_shadow_map: Res<PointLightShadowMap>,
1001 directional_light_shadow_map: Res<DirectionalLightShadowMap>,
1002 mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
1003 (
1004 mut max_directional_lights_warning_emitted,
1005 mut max_rect_lights_warning_emitted,
1006 mut max_cascades_per_light_warning_emitted,
1007 mut live_shadow_mapping_lights,
1008 ): (
1009 Local<bool>,
1010 Local<bool>,
1011 Local<bool>,
1012 Local<HashSet<RetainedViewEntity>>,
1013 ),
1014 (
1015 mut point_lights,
1016 changed_point_lights,
1017 directional_lights,
1018 rect_lights,
1019 mut directional_light_view_entities,
1020 ): (
1021 Query<(
1022 Entity,
1023 &MainEntity,
1024 &ExtractedPointLight,
1025 &mut PointAndSpotLightViewEntities,
1026 AnyOf<(&CubemapFrusta, &Frustum)>,
1027 )>,
1028 Query<
1029 (),
1030 Or<(
1031 Changed<ExtractedPointLight>,
1032 Changed<CubemapFrusta>,
1033 Changed<Frustum>,
1034 )>,
1035 >,
1036 Query<(Entity, &MainEntity, &ExtractedDirectionalLight)>,
1037 Query<(Entity, &MainEntity, &ExtractedRectLight)>,
1038 Query<&mut DirectionalLightViewEntities>,
1039 ),
1040 sorted_cameras: Res<SortedCameras>,
1041 (gpu_preprocessing_support, decals): (
1042 Res<GpuPreprocessingSupport>,
1043 Option<Res<RenderClusteredDecals>>,
1044 ),
1045 (existing_shadow_views, mut light_key_cache, mut specialized_shadow_material_pipeline_cache): (
1046 Query<&ShadowView>,
1047 ResMut<LightKeyCache>,
1048 ResMut<SpecializedShadowMaterialPipelineCache>,
1049 ),
1050) {
1051 let views_iter = views.iter();
1052 let views_count = views_iter.len();
1053 let Some(mut view_gpu_lights_writer) =
1054 light_meta
1055 .view_gpu_lights
1056 .get_writer(views_count, &render_device, &render_queue)
1057 else {
1058 return;
1059 };
1060
1061 let cube_face_rotations = CUBE_MAP_FACES
1063 .iter()
1064 .map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
1065 .collect::<Vec<_>>();
1066
1067 global_clusterable_object_meta.entity_to_index.clear();
1068
1069 let mut point_light_entities: Vec<_> = point_lights
1070 .iter()
1071 .map(|(entity, _, _, _, _)| entity)
1072 .collect::<Vec<_>>();
1073 let mut directional_light_entities: Vec<_> = directional_lights
1074 .iter()
1075 .map(|(entity, _, _)| entity)
1076 .collect::<Vec<_>>();
1077 let rect_light_entities: Vec<_> = rect_lights
1078 .iter()
1079 .map(|(entity, _, _)| entity)
1080 .collect::<Vec<_>>();
1081
1082 #[cfg(any(
1083 not(feature = "webgl"),
1084 not(target_arch = "wasm32"),
1085 feature = "webgpu"
1086 ))]
1087 let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize;
1088 #[cfg(any(
1089 not(feature = "webgl"),
1090 not(target_arch = "wasm32"),
1091 feature = "webgpu"
1092 ))]
1093 let max_texture_cubes = max_texture_array_layers / 6;
1094 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1095 let max_texture_array_layers = 1;
1096 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1097 let max_texture_cubes = 1;
1098
1099 if !*max_directional_lights_warning_emitted
1100 && directional_light_entities.len() > MAX_DIRECTIONAL_LIGHTS
1101 {
1102 warn!(
1103 "The amount of directional lights of {} is exceeding the supported limit of {}.",
1104 directional_light_entities.len(),
1105 MAX_DIRECTIONAL_LIGHTS
1106 );
1107 *max_directional_lights_warning_emitted = true;
1108 }
1109
1110 if !*max_rect_lights_warning_emitted && rect_light_entities.len() > MAX_RECT_LIGHTS {
1111 warn!(
1112 "The amount of rectangle area lights of {} is exceeding the supported limit of {}.",
1113 rect_light_entities.len(),
1114 MAX_RECT_LIGHTS
1115 );
1116 *max_rect_lights_warning_emitted = true;
1117 }
1118
1119 if !*max_cascades_per_light_warning_emitted
1120 && directional_lights
1121 .iter()
1122 .any(|(_, _, light)| light.cascade_shadow_config.bounds.len() > MAX_CASCADES_PER_LIGHT)
1123 {
1124 warn!(
1125 "The number of cascades configured for a directional light exceeds the supported limit of {}.",
1126 MAX_CASCADES_PER_LIGHT
1127 );
1128 *max_cascades_per_light_warning_emitted = true;
1129 }
1130
1131 let point_light_count = point_lights
1132 .iter()
1133 .filter(|light| light.2.spot_light_angles.is_none())
1134 .count();
1135
1136 let point_light_volumetric_enabled_count = point_lights
1137 .iter()
1138 .filter(|(_, _, light, _, _)| light.volumetric && light.spot_light_angles.is_none())
1139 .count()
1140 .min(max_texture_cubes);
1141
1142 let point_light_shadow_maps_count = point_lights
1143 .iter()
1144 .filter(|light| light.2.shadow_maps_enabled && light.2.spot_light_angles.is_none())
1145 .count()
1146 .min(max_texture_cubes);
1147
1148 let directional_volumetric_enabled_count = directional_lights
1149 .iter()
1150 .take(MAX_DIRECTIONAL_LIGHTS)
1151 .filter(|(_, _, light)| light.volumetric)
1152 .count()
1153 .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
1154
1155 let directional_shadow_enabled_count = directional_lights
1156 .iter()
1157 .take(MAX_DIRECTIONAL_LIGHTS)
1158 .filter(|(_, _, light)| light.shadow_maps_enabled)
1159 .count()
1160 .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
1161
1162 let spot_light_count = point_lights
1163 .iter()
1164 .filter(|(_, _, light, _, _)| light.spot_light_angles.is_some())
1165 .count()
1166 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
1167
1168 let spot_light_volumetric_enabled_count = point_lights
1169 .iter()
1170 .filter(|(_, _, light, _, _)| light.volumetric && light.spot_light_angles.is_some())
1171 .count()
1172 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
1173
1174 let spot_light_shadow_maps_count = point_lights
1175 .iter()
1176 .filter(|(_, _, light, _, _)| {
1177 light.shadow_maps_enabled && light.spot_light_angles.is_some()
1178 })
1179 .count()
1180 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
1181
1182 point_light_entities.sort_by_cached_key(|entity| {
1188 (
1189 point_or_spot_light_to_clusterable(point_lights.get(*entity).unwrap().2).ordering(),
1190 *entity,
1191 )
1192 });
1193
1194 directional_light_entities.sort_by_cached_key(|entity| {
1203 let light = directional_lights.get(*entity).unwrap().2;
1204 (light.volumetric, light.shadow_maps_enabled, *entity)
1205 });
1206
1207 if global_clusterable_object_meta.entity_to_index.capacity() < point_light_entities.len() {
1208 global_clusterable_object_meta
1209 .entity_to_index
1210 .reserve(point_light_entities.len());
1211 }
1212
1213 global_clusterable_object_meta.gpu_clustered_lights.clear();
1214
1215 for (index, entity) in point_light_entities.iter().enumerate() {
1216 let light = point_lights.get(*entity).unwrap().2;
1217
1218 let mut flags = PointLightFlags::NONE;
1219
1220 if light.shadow_maps_enabled
1222 && (index < point_light_shadow_maps_count
1223 || (light.spot_light_angles.is_some()
1224 && index - point_light_count < spot_light_shadow_maps_count))
1225 {
1226 flags |= PointLightFlags::SHADOW_MAPS_ENABLED;
1227 }
1228
1229 if light.contact_shadows_enabled {
1230 flags |= PointLightFlags::CONTACT_SHADOWS_ENABLED;
1231 }
1232
1233 let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
1234 core::f32::consts::FRAC_PI_2,
1235 1.0,
1236 light.shadow_map_near_z,
1237 );
1238 if light.shadow_maps_enabled
1239 && light.volumetric
1240 && (index < point_light_volumetric_enabled_count
1241 || (light.spot_light_angles.is_some()
1242 && index - point_light_count < spot_light_volumetric_enabled_count))
1243 {
1244 flags |= PointLightFlags::VOLUMETRIC;
1245 }
1246
1247 if light.affects_lightmapped_mesh_diffuse {
1248 flags |= PointLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1249 }
1250
1251 let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles {
1252 Some((inner, outer)) => {
1253 flags |= PointLightFlags::SPOT_LIGHT;
1254
1255 let light_direction = light.transform.forward();
1256 if light_direction.y.is_sign_negative() {
1257 flags |= PointLightFlags::SPOT_LIGHT_Y_NEGATIVE;
1258 }
1259
1260 let cos_outer = ops::cos(outer);
1261 let spot_scale = 1.0 / f32::max(ops::cos(inner) - cos_outer, 1e-4);
1262 let spot_offset = -cos_outer * spot_scale;
1263
1264 (
1265 light_direction.xz().extend(spot_scale).extend(spot_offset),
1267 ops::tan(outer),
1268 )
1269 }
1270 None => {
1271 (
1272 Vec4::new(
1274 cube_face_projection.z_axis.z,
1275 cube_face_projection.z_axis.w,
1276 cube_face_projection.w_axis.z,
1277 cube_face_projection.w_axis.w,
1278 ),
1279 0.0,
1281 )
1282 }
1283 };
1284
1285 global_clusterable_object_meta
1286 .gpu_clustered_lights
1287 .add(GpuClusteredLight {
1288 light_custom_data,
1289 color_inverse_square_range: (Vec4::from_slice(&light.color.to_f32_array())
1292 * light.intensity)
1293 .xyz()
1294 .extend(1.0 / (light.range * light.range)),
1295 position_radius: light.transform.translation().extend(light.radius),
1296 flags: flags.bits(),
1297 shadow_depth_bias: light.shadow_depth_bias,
1298 shadow_normal_bias: light.shadow_normal_bias,
1299 shadow_map_near_z: light.shadow_map_near_z,
1300 spot_light_tan_angle,
1301 decal_index: decals
1302 .as_ref()
1303 .and_then(|decals| decals.get(*entity))
1304 .and_then(|index| index.try_into().ok())
1305 .unwrap_or(u32::MAX),
1306 range: light.range,
1307 soft_shadow_size: if light.soft_shadows_enabled {
1308 light.radius
1309 } else {
1310 0.0
1311 },
1312 });
1313 global_clusterable_object_meta
1314 .entity_to_index
1315 .insert(*entity, index);
1316 debug_assert_eq!(
1317 global_clusterable_object_meta.entity_to_index.len(),
1318 global_clusterable_object_meta.gpu_clustered_lights.len()
1319 );
1320 }
1321
1322 let mut num_directional_cascades_enabled = 0usize;
1324 for (
1325 _entity,
1326 _camera_main_entity,
1327 _extracted_view,
1328 _clusters,
1329 maybe_layers,
1330 _no_indirect_drawing,
1331 _maybe_ambient_override,
1332 ) in sorted_cameras
1333 .0
1334 .iter()
1335 .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
1336 {
1337 let mut num_directional_cascades_for_this_view = 0usize;
1338 let render_layers = maybe_layers.unwrap_or_default();
1339
1340 for light_entity in directional_light_entities.iter() {
1341 let light = directional_lights.get(*light_entity).unwrap().2;
1342 if light.shadow_maps_enabled && light.render_layers.intersects(render_layers) {
1343 num_directional_cascades_for_this_view += light
1344 .cascade_shadow_config
1345 .bounds
1346 .len()
1347 .min(MAX_CASCADES_PER_LIGHT);
1348 }
1349 }
1350
1351 num_directional_cascades_enabled = num_directional_cascades_enabled
1352 .max(num_directional_cascades_for_this_view)
1353 .min(max_texture_array_layers);
1354 }
1355
1356 global_clusterable_object_meta
1357 .gpu_clustered_lights
1358 .write_buffer(&render_device, &render_queue);
1359
1360 live_shadow_mapping_lights.clear();
1361
1362 let mut point_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1363 let mut directional_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1364
1365 let point_light_depth_texture = texture_cache.get(
1366 &render_device,
1367 TextureDescriptor {
1368 size: Extent3d {
1369 width: point_light_shadow_map.size as u32,
1370 height: point_light_shadow_map.size as u32,
1371 depth_or_array_layers: point_light_shadow_maps_count.max(1) as u32 * 6,
1372 },
1373 mip_level_count: 1,
1374 sample_count: 1,
1375 dimension: TextureDimension::D2,
1376 format: CORE_3D_DEPTH_FORMAT,
1377 label: Some("point_light_shadow_map_texture"),
1378 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1379 view_formats: &[],
1380 },
1381 );
1382
1383 let point_light_depth_texture_view =
1384 point_light_depth_texture
1385 .texture
1386 .create_view(&TextureViewDescriptor {
1387 label: Some("point_light_shadow_map_array_texture_view"),
1388 format: None,
1389 #[cfg(all(
1392 not(target_abi = "sim"),
1393 any(
1394 not(feature = "webgl"),
1395 not(target_arch = "wasm32"),
1396 feature = "webgpu"
1397 )
1398 ))]
1399 dimension: Some(TextureViewDimension::CubeArray),
1400 #[cfg(any(
1401 target_abi = "sim",
1402 all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
1403 ))]
1404 dimension: Some(TextureViewDimension::Cube),
1405 usage: None,
1406 aspect: TextureAspect::DepthOnly,
1407 base_mip_level: 0,
1408 mip_level_count: None,
1409 base_array_layer: 0,
1410 array_layer_count: None,
1411 });
1412
1413 let directional_light_depth_texture = texture_cache.get(
1414 &render_device,
1415 TextureDescriptor {
1416 size: Extent3d {
1417 width: (directional_light_shadow_map.size as u32)
1418 .min(render_device.limits().max_texture_dimension_2d),
1419 height: (directional_light_shadow_map.size as u32)
1420 .min(render_device.limits().max_texture_dimension_2d),
1421 depth_or_array_layers: (num_directional_cascades_enabled
1422 + spot_light_shadow_maps_count)
1423 .max(1) as u32,
1424 },
1425 mip_level_count: 1,
1426 sample_count: 1,
1427 dimension: TextureDimension::D2,
1428 format: CORE_3D_DEPTH_FORMAT,
1429 label: Some("directional_light_shadow_map_texture"),
1430 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1431 view_formats: &[],
1432 },
1433 );
1434
1435 let directional_light_depth_texture_view =
1436 directional_light_depth_texture
1437 .texture
1438 .create_view(&TextureViewDescriptor {
1439 label: Some("directional_light_shadow_map_array_texture_view"),
1440 format: None,
1441 #[cfg(any(
1442 not(feature = "webgl"),
1443 not(target_arch = "wasm32"),
1444 feature = "webgpu"
1445 ))]
1446 dimension: Some(TextureViewDimension::D2Array),
1447 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1448 dimension: Some(TextureViewDimension::D2),
1449 usage: None,
1450 aspect: TextureAspect::DepthOnly,
1451 base_mip_level: 0,
1452 mip_level_count: None,
1453 base_array_layer: 0,
1454 array_layer_count: None,
1455 });
1456
1457 let mut live_views = EntityHashSet::with_capacity(views_count);
1458
1459 for light_entity in point_light_entities
1461 .iter()
1462 .take(point_light_count.min(max_texture_cubes))
1464 {
1465 let (
1466 _,
1467 light_main_entity,
1468 light,
1469 mut point_and_spot_light_view_entities,
1470 (point_light_frusta, _),
1471 ) = point_lights.get_mut(*light_entity).unwrap();
1472
1473 if !light.shadow_maps_enabled {
1474 despawn_entities(
1475 &mut commands,
1476 mem::take(&mut point_and_spot_light_view_entities.0),
1477 );
1478 continue;
1479 }
1480
1481 if point_and_spot_light_view_entities.0.is_empty() {
1482 let light_view_entities: Vec<_> = (0..6).map(|_| commands.spawn_empty().id()).collect();
1484
1485 create_point_shadow_maps(
1486 &mut commands,
1487 &mut point_light_depth_attachments,
1488 &global_clusterable_object_meta,
1489 (
1490 &cube_face_rotations,
1491 point_light_frusta,
1492 &light_view_entities,
1493 ),
1494 &point_light_depth_texture,
1495 (light_entity, light_main_entity, light),
1496 point_light_shadow_map.size as u32,
1497 gpu_preprocessing_support.max_supported_mode,
1498 );
1499
1500 point_and_spot_light_view_entities.0 = light_view_entities;
1501 } else if changed_point_lights.get(*light_entity).is_ok() {
1502 create_point_shadow_maps(
1504 &mut commands,
1505 &mut point_light_depth_attachments,
1506 &global_clusterable_object_meta,
1507 (
1508 &cube_face_rotations,
1509 point_light_frusta,
1510 &point_and_spot_light_view_entities.0,
1511 ),
1512 &point_light_depth_texture,
1513 (light_entity, light_main_entity, light),
1514 point_light_shadow_map.size as u32,
1515 gpu_preprocessing_support.max_supported_mode,
1516 );
1517 }
1518
1519 for face_index in 0..6 {
1522 let retained_view_entity =
1523 RetainedViewEntity::new(*light_main_entity, None, face_index);
1524 shadow_render_phases.prepare_for_new_frame(
1525 retained_view_entity,
1526 gpu_preprocessing_support.max_supported_mode,
1527 );
1528 live_shadow_mapping_lights.insert(retained_view_entity);
1529 }
1530 }
1531
1532 for (light_index, light_entity) in point_light_entities
1534 .iter()
1535 .skip(point_light_count)
1536 .take(spot_light_count)
1537 .enumerate()
1538 {
1539 let (
1540 _,
1541 light_main_entity,
1542 light,
1543 mut point_and_spot_light_view_entities,
1544 (_, spot_light_frustum),
1545 ) = point_lights.get_mut(*light_entity).unwrap();
1546
1547 if !light.shadow_maps_enabled {
1548 despawn_entities(
1549 &mut commands,
1550 mem::take(&mut point_and_spot_light_view_entities.0),
1551 );
1552 continue;
1553 }
1554
1555 let retained_view_entity = RetainedViewEntity::new(*light_main_entity, None, 0);
1558
1559 if point_and_spot_light_view_entities.0.is_empty() {
1560 let view_light_entity = commands.spawn_empty().id();
1561
1562 create_spot_shadow_map(
1563 &mut commands,
1564 &mut directional_light_depth_attachments,
1565 (num_directional_cascades_enabled, light_index),
1566 &directional_light_depth_texture,
1567 view_light_entity,
1568 (light_entity, light_main_entity, light),
1569 spot_light_frustum,
1570 directional_light_shadow_map.size as u32,
1571 gpu_preprocessing_support.max_supported_mode,
1572 );
1573
1574 point_and_spot_light_view_entities.0 = vec![view_light_entity];
1575 } else if changed_point_lights.get(*light_entity).is_ok() {
1576 create_spot_shadow_map(
1577 &mut commands,
1578 &mut directional_light_depth_attachments,
1579 (num_directional_cascades_enabled, light_index),
1580 &directional_light_depth_texture,
1581 point_and_spot_light_view_entities.0[0],
1583 (light_entity, light_main_entity, light),
1584 spot_light_frustum,
1585 directional_light_shadow_map.size as u32,
1586 gpu_preprocessing_support.max_supported_mode,
1587 );
1588 }
1589
1590 shadow_render_phases.prepare_for_new_frame(
1591 retained_view_entity,
1592 gpu_preprocessing_support.max_supported_mode,
1593 );
1594 live_shadow_mapping_lights.insert(retained_view_entity);
1595 }
1596
1597 for (
1599 entity,
1600 camera_main_entity,
1601 extracted_view,
1602 clusters,
1603 maybe_layers,
1604 no_indirect_drawing,
1605 maybe_ambient_override,
1606 ) in sorted_cameras
1607 .0
1608 .iter()
1609 .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
1610 {
1611 live_views.insert(entity);
1612
1613 let view_layers = maybe_layers.unwrap_or_default();
1614 let mut view_lights = Vec::new();
1615 let mut view_occlusion_culling_lights = Vec::new();
1616
1617 let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
1618 GpuPreprocessingMode::Culling
1619 } else {
1620 GpuPreprocessingMode::PreprocessingOnly
1621 });
1622
1623 let is_orthographic = extracted_view.clip_from_view.w_axis.w == 1.0;
1624 let cluster_factors_zw = calculate_cluster_factors(
1625 clusters.near,
1626 clusters.far,
1627 clusters.dimensions.z as f32,
1628 is_orthographic,
1629 );
1630
1631 let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
1632 let ambient_light = AmbientLight {
1633 color: ambient_light.color,
1634 brightness: ambient_light.brightness,
1635 affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes,
1636 };
1637 let ambient_light = maybe_ambient_override.unwrap_or(&ambient_light);
1638
1639 let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
1640 let mut num_directional_cascades_enabled_for_this_view = 0usize;
1641 let mut num_directional_lights_for_this_view = 0usize;
1642 for (index, light_entity) in directional_light_entities
1643 .iter()
1644 .filter(|light_entity| {
1645 directional_lights
1646 .get(**light_entity)
1647 .unwrap()
1648 .2
1649 .render_layers
1650 .intersects(view_layers)
1651 })
1652 .enumerate()
1653 .take(MAX_DIRECTIONAL_LIGHTS)
1654 {
1655 let light = directional_lights.get(*light_entity).unwrap().2;
1656
1657 num_directional_lights_for_this_view += 1;
1658
1659 let mut flags = DirectionalLightFlags::NONE;
1660
1661 if light.volumetric
1663 && light.shadow_maps_enabled
1664 && (index < directional_volumetric_enabled_count)
1665 {
1666 flags |= DirectionalLightFlags::VOLUMETRIC;
1667 }
1668
1669 let mut num_cascades = 0;
1671 if light.shadow_maps_enabled {
1672 let cascades = light
1673 .cascade_shadow_config
1674 .bounds
1675 .len()
1676 .min(MAX_CASCADES_PER_LIGHT);
1677
1678 if num_directional_cascades_enabled_for_this_view + cascades
1679 <= max_texture_array_layers
1680 {
1681 flags |= DirectionalLightFlags::SHADOW_MAPS_ENABLED;
1682 num_cascades += cascades;
1683 }
1684 }
1685
1686 if light.contact_shadows_enabled {
1687 flags |= DirectionalLightFlags::CONTACT_SHADOWS_ENABLED;
1688 }
1689
1690 if light.affects_lightmapped_mesh_diffuse {
1691 flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1692 }
1693
1694 gpu_directional_lights[index] = GpuDirectionalLight {
1695 cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1697 color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1700 dir_to_light: light.transform.back().into(),
1702 flags: flags.bits(),
1703 soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
1704 shadow_depth_bias: light.shadow_depth_bias,
1705 shadow_normal_bias: light.shadow_normal_bias,
1706 num_cascades: num_cascades as u32,
1707 cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
1708 depth_texture_base_index: num_directional_cascades_enabled_for_this_view as u32,
1709 sun_disk_angular_size: light.sun_disk_angular_size,
1710 sun_disk_intensity: light.sun_disk_intensity,
1711 decal_index: decals
1712 .as_ref()
1713 .and_then(|decals| decals.get(*light_entity))
1714 .and_then(|index| index.try_into().ok())
1715 .unwrap_or(u32::MAX),
1716 };
1717 num_directional_cascades_enabled_for_this_view += num_cascades;
1718 }
1719
1720 let mut gpu_lights = GpuLights {
1721 directional_lights: gpu_directional_lights,
1722 ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
1723 * ambient_light.brightness,
1724 cluster_factors: Vec4::new(
1725 clusters.dimensions.x as f32 / extracted_view.viewport.z as f32,
1726 clusters.dimensions.y as f32 / extracted_view.viewport.w as f32,
1727 cluster_factors_zw.x,
1728 cluster_factors_zw.y,
1729 ),
1730 cluster_dimensions: clusters.dimensions.extend(n_clusters),
1731 n_directional_lights: num_directional_lights_for_this_view as u32,
1732 spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
1736 - point_light_count as i32,
1737 ambient_light_affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes
1738 as u32,
1739 n_rect_lights: 0,
1740 rect_lights: [GpuRectLight::default(); MAX_RECT_LIGHTS],
1741 };
1742
1743 for light_entity in directional_light_entities.iter().filter(|light_entity| {
1746 !directional_lights
1747 .get(**light_entity)
1748 .unwrap()
1749 .2
1750 .render_layers
1751 .intersects(view_layers)
1752 }) {
1753 let Ok(mut light_view_entities) =
1754 directional_light_view_entities.get_mut(*light_entity)
1755 else {
1756 continue;
1757 };
1758 if let Some(entities) = light_view_entities.remove(&entity) {
1759 despawn_entities(&mut commands, entities);
1760 }
1761 }
1762
1763 let mut directional_depth_texture_array_index = 0u32;
1764 for (light_index, light_entity) in directional_light_entities
1765 .iter()
1766 .filter(|light_entity| {
1767 directional_lights
1768 .get(**light_entity)
1769 .unwrap()
1770 .2
1771 .render_layers
1772 .intersects(view_layers)
1773 })
1774 .enumerate()
1775 .take(MAX_DIRECTIONAL_LIGHTS)
1776 {
1777 let (_, light_main_entity, light) = directional_lights.get(*light_entity).unwrap();
1778
1779 let Ok(mut light_view_entities) =
1780 directional_light_view_entities.get_mut(*light_entity)
1781 else {
1782 continue;
1783 };
1784
1785 let gpu_light = &mut gpu_lights.directional_lights[light_index];
1786
1787 if (gpu_light.flags & DirectionalLightFlags::SHADOW_MAPS_ENABLED.bits()) == 0u32 {
1789 if let Some(entities) = light_view_entities.remove(&entity) {
1790 despawn_entities(&mut commands, entities);
1791 }
1792 continue;
1793 }
1794
1795 let cascades = light
1796 .cascades
1797 .get(&entity)
1798 .unwrap()
1799 .iter()
1800 .take(MAX_CASCADES_PER_LIGHT);
1801 let frusta = light
1802 .frusta
1803 .get(&entity)
1804 .unwrap()
1805 .iter()
1806 .take(MAX_CASCADES_PER_LIGHT);
1807
1808 let iter = cascades
1809 .zip(frusta)
1810 .zip(&light.cascade_shadow_config.bounds);
1811
1812 let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
1813 (0..iter.len())
1814 .map(|_| commands.spawn_empty().id())
1815 .collect()
1816 });
1817 if light_view_entities.len() != iter.len() {
1818 let entities = mem::take(light_view_entities);
1819 despawn_entities(&mut commands, entities);
1820 light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
1821 }
1822
1823 for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
1824 iter.zip(light_view_entities.iter().copied()).enumerate()
1825 {
1826 gpu_lights.directional_lights[light_index].cascades[cascade_index] =
1827 GpuDirectionalCascade {
1828 clip_from_world: cascade.clip_from_world,
1829 texel_size: cascade.texel_size,
1830 far_bound: *bound,
1831 };
1832
1833 let depth_texture_view =
1834 directional_light_depth_texture
1835 .texture
1836 .create_view(&TextureViewDescriptor {
1837 label: Some("directional_light_shadow_map_array_texture_view"),
1838 format: None,
1839 dimension: Some(TextureViewDimension::D2),
1840 usage: None,
1841 aspect: TextureAspect::All,
1842 base_mip_level: 0,
1843 mip_level_count: None,
1844 base_array_layer: directional_depth_texture_array_index,
1845 array_layer_count: Some(1u32),
1846 });
1847
1848 let depth_attachment = DepthAttachment::new(depth_texture_view.clone(), Some(0.0));
1852
1853 directional_depth_texture_array_index += 1;
1854
1855 let mut frustum = *frustum;
1856 frustum.half_spaces[ViewFrustum::NEAR_PLANE_IDX] = HalfSpace::new(
1858 frustum.half_spaces[ViewFrustum::NEAR_PLANE_IDX]
1859 .normal()
1860 .extend(f32::INFINITY),
1861 );
1862
1863 let retained_view_entity = RetainedViewEntity::new(
1864 *light_main_entity,
1865 Some(camera_main_entity.into()),
1866 cascade_index as u32,
1867 );
1868
1869 commands.entity(view_light_entity).insert((
1870 ShadowView {
1871 depth_attachment,
1872 pass_name: format!(
1873 "shadow_directional_light_{light_index}_cascade_{cascade_index}"
1874 ),
1875 },
1876 ExtractedView {
1877 retained_view_entity,
1878 viewport: UVec4::new(
1879 0,
1880 0,
1881 directional_light_shadow_map.size as u32,
1882 directional_light_shadow_map.size as u32,
1883 ),
1884 world_from_view: GlobalTransform::from(cascade.world_from_cascade),
1885 clip_from_view: cascade.clip_from_cascade,
1886 clip_from_world: Some(cascade.clip_from_world),
1887 target_format: CORE_3D_DEPTH_FORMAT,
1888 color_grading: Default::default(),
1889 invert_culling: false,
1890 },
1891 frustum,
1892 LightEntity::Directional {
1893 light_entity: *light_entity,
1894 cascade_index,
1895 },
1896 ));
1897
1898 if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1899 commands.entity(view_light_entity).insert(NoIndirectDrawing);
1900 }
1901
1902 view_lights.push(view_light_entity);
1903
1904 if light.occlusion_culling {
1906 commands.entity(view_light_entity).insert((
1907 OcclusionCulling,
1908 OcclusionCullingSubview {
1909 depth_texture_view,
1910 depth_texture_size: directional_light_shadow_map.size as u32,
1911 },
1912 ));
1913 view_occlusion_culling_lights.push(view_light_entity);
1914 }
1915
1916 shadow_render_phases
1920 .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1921 live_shadow_mapping_lights.insert(retained_view_entity);
1922 }
1923 }
1924
1925 if !view_occlusion_culling_lights.is_empty() {
1928 commands
1929 .entity(entity)
1930 .insert(OcclusionCullingSubviewEntities(
1931 view_occlusion_culling_lights,
1932 ));
1933 }
1934
1935 gpu_lights.n_rect_lights = 0;
1943 for (index, (_, _, rect_light)) in rect_lights.iter().enumerate().take(MAX_RECT_LIGHTS) {
1944 let right = rect_light.transform.right().into();
1945 let up = rect_light.transform.up().into();
1946 gpu_lights.rect_lights[index] = GpuRectLight {
1947 color: Vec4::from_slice(&rect_light.color.to_f32_array()) * rect_light.intensity,
1948 position: rect_light.transform.translation(),
1949 right,
1950 up,
1951 width: rect_light.width,
1952 height: rect_light.height,
1953 range: rect_light.range,
1954 };
1955 gpu_lights.n_rect_lights += 1;
1956 }
1957
1958 commands.entity(entity).insert((
1959 ViewShadowBindings {
1960 point_light_depth_texture: point_light_depth_texture.texture.clone(),
1961 point_light_depth_texture_view: point_light_depth_texture_view.clone(),
1962 directional_light_depth_texture: directional_light_depth_texture.texture.clone(),
1963 directional_light_depth_texture_view: directional_light_depth_texture_view.clone(),
1964 },
1965 ViewLightEntities {
1966 lights: view_lights,
1967 },
1968 ViewLightsUniformOffset {
1969 offset: view_gpu_lights_writer.write(&gpu_lights),
1970 },
1971 ));
1972 }
1973
1974 for existing_shadow_view in &existing_shadow_views {
1977 existing_shadow_view
1978 .depth_attachment
1979 .prepare_for_new_frame();
1980 }
1981
1982 for mut entities in &mut directional_light_view_entities {
1984 for (_, light_view_entities) in
1985 entities.extract_if(|entity, _| !live_views.contains(entity))
1986 {
1987 despawn_entities(&mut commands, light_view_entities);
1988 }
1989 }
1990
1991 shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
1992 light_key_cache.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
1993 specialized_shadow_material_pipeline_cache
1994 .retain(|entity, _| live_shadow_mapping_lights.contains(entity));
1995}
1996
1997fn create_point_shadow_maps(
2001 commands: &mut Commands,
2002 point_light_depth_attachments: &mut HashMap<u32, DepthAttachment>,
2003 global_clusterable_object_meta: &ResMut<GlobalClusterableObjectMeta>,
2004 (cube_face_rotations, point_light_frusta, light_view_entities): (
2005 &Vec<Transform>,
2006 Option<&CubemapFrusta>,
2007 &Vec<Entity>,
2008 ),
2009 point_light_depth_texture: &CachedTexture,
2010 (light_entity, light_main_entity, light): (&Entity, &MainEntity, &ExtractedPointLight),
2011 point_light_shadow_map_size: u32,
2012 gpu_preprocessing_support_max_supported_mode: GpuPreprocessingMode,
2013) {
2014 let light_index = *global_clusterable_object_meta
2015 .entity_to_index
2016 .get(light_entity)
2017 .unwrap();
2018 let view_translation = GlobalTransform::from_translation(light.transform.translation());
2022
2023 let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
2024 core::f32::consts::FRAC_PI_2,
2025 1.0,
2026 light.shadow_map_near_z,
2027 );
2028
2029 for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
2030 .iter()
2031 .zip(&point_light_frusta.unwrap().frusta)
2032 .zip(light_view_entities.iter().copied())
2033 .enumerate()
2034 {
2035 let base_array_layer = (light_index * 6 + face_index) as u32;
2036
2037 let depth_attachment = point_light_depth_attachments
2038 .entry(base_array_layer)
2039 .or_insert_with(|| {
2040 let depth_texture_view =
2041 point_light_depth_texture
2042 .texture
2043 .create_view(&TextureViewDescriptor {
2044 label: Some("point_light_shadow_map_texture_view"),
2045 format: None,
2046 dimension: Some(TextureViewDimension::D2),
2047 usage: None,
2048 aspect: TextureAspect::All,
2049 base_mip_level: 0,
2050 mip_level_count: None,
2051 base_array_layer,
2052 array_layer_count: Some(1u32),
2053 });
2054
2055 DepthAttachment::new(depth_texture_view, Some(0.0))
2056 })
2057 .clone();
2058
2059 let retained_view_entity =
2062 RetainedViewEntity::new(*light_main_entity, None, face_index as u32);
2063
2064 commands.entity(view_light_entity).insert((
2065 ShadowView {
2066 depth_attachment,
2067 pass_name: format!(
2068 "shadow_point_light_{}_{}",
2069 light_index,
2070 face_index_to_name(face_index)
2071 ),
2072 },
2073 ExtractedView {
2074 retained_view_entity,
2075 viewport: UVec4::new(
2076 0,
2077 0,
2078 point_light_shadow_map_size,
2079 point_light_shadow_map_size,
2080 ),
2081 world_from_view: view_translation * *view_rotation,
2082 clip_from_world: None,
2083 clip_from_view: cube_face_projection,
2084 target_format: CORE_3D_DEPTH_FORMAT,
2085 color_grading: Default::default(),
2086 invert_culling: false,
2087 },
2088 *frustum,
2089 LightEntity::Point {
2090 light_entity: *light_entity,
2091 face_index,
2092 },
2093 RootNonCameraView(Core3d.intern()),
2094 ));
2095
2096 if !matches!(
2097 gpu_preprocessing_support_max_supported_mode,
2098 GpuPreprocessingMode::Culling
2099 ) {
2100 commands.entity(view_light_entity).insert(NoIndirectDrawing);
2101 }
2102 }
2103}
2104
2105fn create_spot_shadow_map(
2108 commands: &mut Commands,
2109 directional_light_depth_attachments: &mut HashMap<u32, DepthAttachment>,
2110 (num_directional_cascades_enabled, light_index): (usize, usize),
2111 directional_light_depth_texture: &CachedTexture,
2112 view_light_entity: Entity,
2113 (light_entity, light_main_entity, light): (&Entity, &MainEntity, &ExtractedPointLight),
2114 spot_light_frustum: Option<&Frustum>,
2115 directional_light_shadow_map_size: u32,
2116 gpu_preprocessing_support_max_supported_mode: GpuPreprocessingMode,
2117) {
2118 let spot_world_from_view = spot_light_world_from_view(&light.transform);
2119 let spot_world_from_view = spot_world_from_view.into();
2120
2121 let angle = light.spot_light_angles.expect("lights should be sorted so that \
2122 [point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
2123 let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
2124
2125 let base_array_layer = (num_directional_cascades_enabled + light_index) as u32;
2126
2127 let depth_attachment = directional_light_depth_attachments
2128 .entry(base_array_layer)
2129 .or_insert_with(|| {
2130 let depth_texture_view =
2131 directional_light_depth_texture
2132 .texture
2133 .create_view(&TextureViewDescriptor {
2134 label: Some("spot_light_shadow_map_texture_view"),
2135 format: None,
2136 dimension: Some(TextureViewDimension::D2),
2137 usage: None,
2138 aspect: TextureAspect::All,
2139 base_mip_level: 0,
2140 mip_level_count: None,
2141 base_array_layer,
2142 array_layer_count: Some(1u32),
2143 });
2144
2145 DepthAttachment::new(depth_texture_view, Some(0.0))
2146 })
2147 .clone();
2148
2149 let retained_view_entity = RetainedViewEntity::new(*light_main_entity, None, 0);
2150 commands.entity(view_light_entity).insert((
2151 ShadowView {
2152 depth_attachment,
2153 pass_name: format!("shadow_spot_light_{light_index}"),
2154 },
2155 ExtractedView {
2156 retained_view_entity,
2157 viewport: UVec4::new(
2158 0,
2159 0,
2160 directional_light_shadow_map_size,
2161 directional_light_shadow_map_size,
2162 ),
2163 world_from_view: spot_world_from_view,
2164 clip_from_view: spot_projection,
2165 clip_from_world: None,
2166 target_format: CORE_3D_DEPTH_FORMAT,
2167 color_grading: Default::default(),
2168 invert_culling: false,
2169 },
2170 *spot_light_frustum.unwrap(),
2171 LightEntity::Spot {
2172 light_entity: *light_entity,
2173 },
2174 RootNonCameraView(Core3d.intern()),
2175 ));
2176
2177 if !matches!(
2178 gpu_preprocessing_support_max_supported_mode,
2179 GpuPreprocessingMode::Culling
2180 ) {
2181 commands.entity(view_light_entity).insert(NoIndirectDrawing);
2182 }
2183}
2184
2185fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
2186 if entities.is_empty() {
2187 return;
2188 }
2189 commands.queue(move |world: &mut World| {
2190 for entity in entities {
2191 world.despawn(entity);
2192 }
2193 });
2194}
2195
2196#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
2197pub struct LightKeyCache(HashMap<RetainedViewEntity, MeshPipelineKey>);
2198
2199#[derive(Resource, Deref, DerefMut, Default)]
2200pub struct SpecializedShadowMaterialPipelineCache {
2201 #[deref]
2203 map: HashMap<RetainedViewEntity, SpecializedShadowMaterialViewPipelineCache>,
2204}
2205
2206#[derive(Deref, DerefMut, Default)]
2207pub struct SpecializedShadowMaterialViewPipelineCache {
2208 #[deref]
2209 map: MainEntityHashMap<(CachedRenderPipelineId, DrawFunctionId)>,
2210}
2211
2212pub fn check_views_lights_need_specialization(
2213 view_light_entities: Query<(&LightEntity, &ExtractedView)>,
2214 shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
2215 mut light_key_cache: ResMut<LightKeyCache>,
2216 mut dirty_specializations: ResMut<DirtySpecializations>,
2217) {
2218 for (light_entity, extracted_view_light) in &view_light_entities {
2219 if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
2220 continue;
2221 }
2222
2223 let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
2224 let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
2225 light_key.set(
2226 MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC | MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO,
2227 is_directional_light,
2228 );
2229 light_key.set(
2230 MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE,
2231 !is_directional_light,
2232 );
2233 if let Some(current_key) =
2234 light_key_cache.get_mut(&extracted_view_light.retained_view_entity)
2235 {
2236 if *current_key != light_key {
2237 light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
2238 dirty_specializations
2239 .views
2240 .insert(extracted_view_light.retained_view_entity);
2241 }
2242 } else {
2243 light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
2244 dirty_specializations
2245 .views
2246 .insert(extracted_view_light.retained_view_entity);
2247 }
2248 }
2249}
2250
2251pub(crate) struct ShadowSpecializationWorkItem {
2252 visible_entity: MainEntity,
2253 retained_view_entity: RetainedViewEntity,
2254 mesh_key: MeshPipelineKey,
2255 layout: MeshVertexBufferLayoutRef,
2256 properties: Arc<MaterialProperties>,
2257 material_type_id: TypeId,
2258}
2259
2260#[derive(Default, Deref, DerefMut, Resource)]
2265pub struct PendingShadowQueues(pub PendingQueues);
2266
2267#[derive(SystemParam)]
2268pub(crate) struct SpecializeShadowsSystemParam<'w, 's> {
2269 render_meshes: Res<'w, RenderAssets<RenderMesh>>,
2270 render_mesh_instances: Res<'w, RenderMeshInstances>,
2271 render_materials: Res<'w, ErasedRenderAssets<PreparedMaterial>>,
2272 render_material_instances: Res<'w, RenderMaterialInstances>,
2273 shadow_render_phases: Res<'w, ViewBinnedRenderPhases<Shadow>>,
2274 render_lightmaps: Res<'w, RenderLightmaps>,
2275 view_light_entities: Query<'w, 's, (&'static LightEntity, &'static ExtractedView)>,
2276 shadow_map_visible_entities_query: Query<'w, 's, &'static RenderShadowMapVisibleEntities>,
2277 light_key_cache: Res<'w, LightKeyCache>,
2278 specialized_shadow_material_pipeline_cache: ResMut<'w, SpecializedShadowMaterialPipelineCache>,
2279 pending_shadow_queues: ResMut<'w, PendingShadowQueues>,
2280 dirty_specializations: Res<'w, DirtySpecializations>,
2281}
2282
2283pub(crate) fn specialize_shadows(
2284 world: &mut World,
2285 state: &mut SystemState<SpecializeShadowsSystemParam>,
2286 mut work_items: Local<Vec<ShadowSpecializationWorkItem>>,
2287 mut all_shadow_views: Local<HashSet<RetainedViewEntity, FixedHasher>>,
2288) {
2289 work_items.clear();
2290 all_shadow_views.clear();
2291
2292 {
2293 let SpecializeShadowsSystemParam {
2294 render_meshes,
2295 render_mesh_instances,
2296 render_materials,
2297 render_material_instances,
2298 shadow_render_phases,
2299 render_lightmaps,
2300 view_light_entities,
2301 shadow_map_visible_entities_query,
2302 light_key_cache,
2303 mut specialized_shadow_material_pipeline_cache,
2304 mut pending_shadow_queues,
2305 dirty_specializations,
2306 } = state.get_mut(world).unwrap();
2307
2308 for (light_entity, extracted_view_light) in &view_light_entities {
2309 all_shadow_views.insert(extracted_view_light.retained_view_entity);
2310
2311 if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
2312 continue;
2313 }
2314 let Some(light_key) = light_key_cache.get(&extracted_view_light.retained_view_entity)
2315 else {
2316 continue;
2317 };
2318
2319 let visible_entities = get_shadow_map_visible_entities(
2320 &shadow_map_visible_entities_query,
2321 light_entity,
2322 extracted_view_light,
2323 );
2324
2325 let mut maybe_specialized_shadow_material_pipeline_cache =
2326 specialized_shadow_material_pipeline_cache
2327 .get_mut(&extracted_view_light.retained_view_entity);
2328
2329 if let Some(ref mut specialized_shadow_material_pipeline_cache) =
2332 maybe_specialized_shadow_material_pipeline_cache
2333 {
2334 if dirty_specializations
2335 .must_wipe_specializations_for_view(extracted_view_light.retained_view_entity)
2336 {
2337 specialized_shadow_material_pipeline_cache.clear();
2338 } else {
2339 for &renderable_entity in dirty_specializations.iter_to_despecialize() {
2340 specialized_shadow_material_pipeline_cache.remove(&renderable_entity);
2341 }
2342 }
2343 }
2344
2345 let view_pending_shadow_queues = pending_shadow_queues
2347 .prepare_for_new_frame(extracted_view_light.retained_view_entity);
2348
2349 let Some(visible_entities) = visible_entities.get::<Mesh3d>() else {
2353 continue;
2354 };
2355
2356 for (render_entity, visible_entity) in dirty_specializations.iter_to_specialize(
2358 extracted_view_light.retained_view_entity,
2359 visible_entities,
2360 &view_pending_shadow_queues.prev_frame,
2361 ) {
2362 if maybe_specialized_shadow_material_pipeline_cache
2363 .as_ref()
2364 .is_some_and(|specialized_shadow_material_pipeline_cache| {
2365 specialized_shadow_material_pipeline_cache.contains_key(visible_entity)
2366 })
2367 {
2368 continue;
2369 }
2370
2371 let Some(material_instance) =
2376 render_material_instances.instances.get(visible_entity)
2377 else {
2378 view_pending_shadow_queues
2379 .current_frame
2380 .insert((*render_entity, *visible_entity));
2381 continue;
2382 };
2383 let Some(mesh_instance) =
2384 render_mesh_instances.render_mesh_queue_data(*visible_entity)
2385 else {
2386 view_pending_shadow_queues
2387 .current_frame
2388 .insert((*render_entity, *visible_entity));
2389 continue;
2390 };
2391 let Some(material) = render_materials.get(material_instance.asset_id) else {
2392 view_pending_shadow_queues
2393 .current_frame
2394 .insert((*render_entity, *visible_entity));
2395 continue;
2396 };
2397
2398 if !material.properties.shadows_enabled {
2399 continue;
2401 }
2402 if !mesh_instance
2403 .flags()
2404 .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
2405 {
2406 continue;
2407 }
2408 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id()) else {
2409 continue;
2410 };
2411
2412 let mut mesh_key =
2413 *light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
2414
2415 if render_lightmaps
2421 .render_lightmaps
2422 .contains_key(visible_entity)
2423 {
2424 mesh_key |= MeshPipelineKey::LIGHTMAPPED;
2425 }
2426
2427 mesh_key |= match material.properties.alpha_mode {
2428 AlphaMode::Mask(_)
2429 | AlphaMode::Blend
2430 | AlphaMode::Premultiplied
2431 | AlphaMode::Add
2432 | AlphaMode::AlphaToCoverage => MeshPipelineKey::MAY_DISCARD,
2433 _ => MeshPipelineKey::NONE,
2434 };
2435
2436 work_items.push(ShadowSpecializationWorkItem {
2437 visible_entity: *visible_entity,
2438 retained_view_entity: extracted_view_light.retained_view_entity,
2439 mesh_key,
2440 layout: mesh.layout.clone(),
2441 properties: material.properties.clone(),
2442 material_type_id: material_instance.asset_id.type_id(),
2443 });
2444 }
2445 }
2446
2447 pending_shadow_queues.expire_stale_views(&all_shadow_views);
2448 }
2449
2450 let depth_clip_control_supported = world
2451 .resource::<PrepassPipeline>()
2452 .depth_clip_control_supported;
2453
2454 for item in work_items.drain(..) {
2455 let Some(prepass_specialize) = item.properties.prepass_specialize else {
2456 continue;
2457 };
2458
2459 let key = ErasedMaterialPipelineKey {
2460 type_id: item.material_type_id,
2461 mesh_key: ErasedMeshPipelineKey::new(item.mesh_key),
2462 material_key: item.properties.material_key.clone(),
2463 };
2464
2465 let emulate_unclipped_depth = item
2466 .mesh_key
2467 .contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
2468 && !depth_clip_control_supported;
2469 let is_depth_only_opaque = !item
2470 .mesh_key
2471 .intersects(MeshPipelineKey::MAY_DISCARD | MeshPipelineKey::PREPASS_READS_MATERIAL)
2472 && !emulate_unclipped_depth;
2473 let draw_function = if is_depth_only_opaque {
2474 item.properties
2475 .get_draw_function(ShadowsDepthOnlyDrawFunction)
2476 } else {
2477 item.properties.get_draw_function(ShadowsDrawFunction)
2478 };
2479
2480 let Some(draw_function) = draw_function else {
2481 continue;
2482 };
2483
2484 match prepass_specialize(world, key, &item.layout, &item.properties) {
2485 Ok(pipeline_id) => {
2486 world
2487 .resource_mut::<SpecializedShadowMaterialPipelineCache>()
2488 .entry(item.retained_view_entity)
2489 .or_default()
2490 .insert(item.visible_entity, (pipeline_id, draw_function));
2491 }
2492 Err(err) => error!("{}", err),
2493 }
2494 }
2495
2496 world
2498 .resource_mut::<SpecializedShadowMaterialPipelineCache>()
2499 .retain(|view, _| all_shadow_views.contains(view));
2500}
2501
2502pub fn queue_shadows(
2506 render_mesh_instances: Res<RenderMeshInstances>,
2507 render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
2508 render_material_instances: Res<RenderMaterialInstances>,
2509 mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
2510 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
2511 mesh_allocator: Res<MeshAllocator>,
2512 view_light_entities: Query<(&LightEntity, &ExtractedView, Option<&RenderLayers>)>,
2513 shadow_map_visible_entities_query: Query<&RenderShadowMapVisibleEntities>,
2514 specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache>,
2515 mut pending_shadow_queues: ResMut<PendingShadowQueues>,
2516 dirty_specializations: Res<DirtySpecializations>,
2517) {
2518 for (light_entity, extracted_view_light, maybe_view_render_layers) in &view_light_entities {
2519 let Some(shadow_phase) =
2520 shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity)
2521 else {
2522 continue;
2523 };
2524
2525 let Some(view_specialized_material_pipeline_cache) =
2526 specialized_material_pipeline_cache.get(&extracted_view_light.retained_view_entity)
2527 else {
2528 continue;
2529 };
2530
2531 let view_pending_shadow_queues = pending_shadow_queues
2533 .get_mut(&extracted_view_light.retained_view_entity)
2534 .expect("View pending shadow queues should have been created in `specialize_shadows`");
2535
2536 let visible_entities = get_shadow_map_visible_entities(
2537 &shadow_map_visible_entities_query,
2538 light_entity,
2539 extracted_view_light,
2540 );
2541
2542 let Some(visible_entities) = visible_entities.get::<Mesh3d>() else {
2543 continue;
2544 };
2545
2546 for &main_entity in dirty_specializations
2548 .iter_to_dequeue(extracted_view_light.retained_view_entity, visible_entities)
2549 {
2550 shadow_phase.remove(main_entity);
2551 }
2552
2553 for (render_entity, main_entity) in dirty_specializations.iter_to_queue(
2555 extracted_view_light.retained_view_entity,
2556 visible_entities,
2557 &view_pending_shadow_queues.prev_frame,
2558 ) {
2559 let Some(&(pipeline_id, draw_function)) =
2560 view_specialized_material_pipeline_cache.get(main_entity)
2561 else {
2562 continue;
2563 };
2564
2565 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*main_entity)
2566 else {
2567 view_pending_shadow_queues
2571 .current_frame
2572 .insert((*render_entity, *main_entity));
2573 continue;
2574 };
2575 if !mesh_instance
2576 .flags()
2577 .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
2578 {
2579 continue;
2580 }
2581
2582 let mesh_layers = mesh_instance.render_layers.as_ref().unwrap_or_default();
2583 let view_render_layers = maybe_view_render_layers.unwrap_or_default();
2584 if !view_render_layers.intersects(mesh_layers) {
2585 continue;
2586 }
2587
2588 let Some(material_instance) = render_material_instances.instances.get(main_entity)
2589 else {
2590 continue;
2591 };
2592 let Some(material) = render_materials.get(material_instance.asset_id) else {
2593 view_pending_shadow_queues
2597 .current_frame
2598 .insert((*render_entity, *main_entity));
2599 continue;
2600 };
2601
2602 let depth_only_draw_function = material
2603 .properties
2604 .get_draw_function(ShadowsDepthOnlyDrawFunction);
2605 let material_bind_group_index = if Some(draw_function) == depth_only_draw_function {
2606 None
2607 } else {
2608 Some(material.binding.group.0)
2609 };
2610
2611 let Some(mesh_slabs) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id()) else {
2612 continue;
2613 };
2614
2615 let batch_set_key = ShadowBatchSetKey {
2616 pipeline: pipeline_id,
2617 draw_function,
2618 material_bind_group_index,
2619 slabs: mesh_slabs,
2620 };
2621
2622 shadow_phase.add(
2623 batch_set_key,
2624 ShadowBinKey {
2625 asset_id: mesh_instance.mesh_asset_id().into(),
2626 },
2627 (*render_entity, *main_entity),
2628 mesh_instance.current_uniform_index,
2629 BinnedRenderPhaseType::mesh(
2630 mesh_instance.should_batch(),
2631 &gpu_preprocessing_support,
2632 ),
2633 );
2634 }
2635 }
2636}
2637
2638pub struct Shadow {
2639 pub batch_set_key: ShadowBatchSetKey,
2644 pub bin_key: ShadowBinKey,
2646 pub representative_entity: (Entity, MainEntity),
2647 pub batch_range: Range<u32>,
2648 pub extra_index: PhaseItemExtraIndex,
2649}
2650
2651#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2657pub struct ShadowBatchSetKey {
2658 pub pipeline: CachedRenderPipelineId,
2660
2661 pub draw_function: DrawFunctionId,
2663
2664 pub material_bind_group_index: Option<u32>,
2668
2669 pub slabs: MeshSlabs,
2676}
2677
2678impl PhaseItemBatchSetKey for ShadowBatchSetKey {
2679 fn indexed(&self) -> bool {
2680 self.slabs.index_slab_id.is_some()
2681 }
2682}
2683
2684#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2686pub struct ShadowBinKey {
2687 pub asset_id: UntypedAssetId,
2689}
2690
2691impl PhaseItem for Shadow {
2692 #[inline]
2693 fn entity(&self) -> Entity {
2694 self.representative_entity.0
2695 }
2696
2697 fn main_entity(&self) -> MainEntity {
2698 self.representative_entity.1
2699 }
2700
2701 #[inline]
2702 fn draw_function(&self) -> DrawFunctionId {
2703 self.batch_set_key.draw_function
2704 }
2705
2706 #[inline]
2707 fn batch_range(&self) -> &Range<u32> {
2708 &self.batch_range
2709 }
2710
2711 #[inline]
2712 fn batch_range_mut(&mut self) -> &mut Range<u32> {
2713 &mut self.batch_range
2714 }
2715
2716 #[inline]
2717 fn extra_index(&self) -> PhaseItemExtraIndex {
2718 self.extra_index.clone()
2719 }
2720
2721 #[inline]
2722 fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
2723 (&mut self.batch_range, &mut self.extra_index)
2724 }
2725}
2726
2727impl BinnedPhaseItem for Shadow {
2728 type BatchSetKey = ShadowBatchSetKey;
2729 type BinKey = ShadowBinKey;
2730
2731 #[inline]
2732 fn new(
2733 batch_set_key: Self::BatchSetKey,
2734 bin_key: Self::BinKey,
2735 representative_entity: (Entity, MainEntity),
2736 batch_range: Range<u32>,
2737 extra_index: PhaseItemExtraIndex,
2738 ) -> Self {
2739 Shadow {
2740 batch_set_key,
2741 bin_key,
2742 representative_entity,
2743 batch_range,
2744 extra_index,
2745 }
2746 }
2747}
2748
2749impl CachedRenderPipelinePhaseItem for Shadow {
2750 #[inline]
2751 fn cached_pipeline(&self) -> CachedRenderPipelineId {
2752 self.batch_set_key.pipeline
2753 }
2754}
2755
2756pub const EARLY_SHADOW_PASS: bool = false;
2757pub const LATE_SHADOW_PASS: bool = true;
2758
2759pub fn shared_shadow_pass<const IS_LATE: bool>(
2763 world: &World,
2764 view_light_query: ViewQuery<(Entity, &ShadowView, &ExtractedView, Has<OcclusionCulling>)>,
2765 shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
2766 mut ctx: RenderContext,
2767) {
2768 let (view_light_entity, view_light, extracted_light_view, occlusion_culling) =
2769 view_light_query.into_inner();
2770 view_shadow_pass::<IS_LATE>(
2771 view_light_entity,
2772 view_light,
2773 extracted_light_view,
2774 occlusion_culling,
2775 world,
2776 &shadow_render_phases,
2777 &mut ctx,
2778 );
2779}
2780
2781pub fn per_view_shadow_pass<const IS_LATE: bool>(
2785 world: &World,
2786 view: ViewQuery<&ViewLightEntities>,
2787 view_light_query: Query<(&ShadowView, &ExtractedView, Has<OcclusionCulling>)>,
2788 shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
2789 mut ctx: RenderContext,
2790) {
2791 let view_lights = view.into_inner();
2792
2793 for view_light_entity in view_lights.lights.iter().copied() {
2794 if let Ok((view_light, extracted_light_view, occlusion_culling)) =
2795 view_light_query.get(view_light_entity)
2796 {
2797 view_shadow_pass::<IS_LATE>(
2798 view_light_entity,
2799 view_light,
2800 extracted_light_view,
2801 occlusion_culling,
2802 world,
2803 &shadow_render_phases,
2804 &mut ctx,
2805 );
2806 }
2807 }
2808}
2809
2810fn view_shadow_pass<const IS_LATE: bool>(
2812 view_light_entity: Entity,
2813 view_light: &ShadowView,
2814 extracted_light_view: &ExtractedView,
2815 occlusion_culling: bool,
2816 world: &World,
2817 shadow_render_phases: &ViewBinnedRenderPhases<Shadow>,
2818 ctx: &mut RenderContext,
2819) {
2820 if IS_LATE && !occlusion_culling {
2821 return;
2822 }
2823
2824 let Some(shadow_phase) = shadow_render_phases.get(&extracted_light_view.retained_view_entity)
2825 else {
2826 return;
2827 };
2828
2829 #[cfg(feature = "trace")]
2830 let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered();
2831
2832 let depth_stencil_attachment = Some(view_light.depth_attachment.get_attachment(StoreOp::Store));
2833
2834 let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
2835 label: Some(&view_light.pass_name),
2836 color_attachments: &[],
2837 depth_stencil_attachment,
2838 timestamp_writes: None,
2839 occlusion_query_set: None,
2840 multiview_mask: None,
2841 });
2842
2843 if let Err(err) = shadow_phase.render(&mut render_pass, world, view_light_entity) {
2844 error!("Error encountered while rendering the shadow phase {err:?}");
2845 }
2846}
2847
2848fn point_or_spot_light_to_clusterable(point_light: &ExtractedPointLight) -> ClusterableObjectType {
2850 match point_light.spot_light_angles {
2851 Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
2852 outer_angle,
2853 shadow_maps_enabled: point_light.shadow_maps_enabled,
2854 volumetric: point_light.volumetric,
2855 },
2856 None => ClusterableObjectType::PointLight {
2857 shadow_maps_enabled: point_light.shadow_maps_enabled,
2858 volumetric: point_light.volumetric,
2859 },
2860 }
2861}
2862
2863fn get_shadow_map_visible_entities<'w, 's: 'w>(
2866 shadow_map_visible_entities_query: &'w Query<'w, 's, &'_ RenderShadowMapVisibleEntities>,
2867 light_entity: &'_ LightEntity,
2868 extracted_view_light: &'_ ExtractedView,
2869) -> &'w RenderVisibleEntities {
2870 match light_entity {
2871 LightEntity::Directional { light_entity, .. } => {
2872 let retained_view_entity = extracted_view_light.retained_view_entity;
2873 shadow_map_visible_entities_query
2874 .get(*light_entity)
2875 .expect("Failed to get directional light visible entities")
2876 .subviews
2877 .get(&retained_view_entity)
2878 .expect("Failed to get directional light visible entities for cascade")
2879 }
2880 LightEntity::Point {
2881 light_entity,
2882 face_index,
2883 } => {
2884 let retained_view_entity = RetainedViewEntity {
2888 main_entity: extracted_view_light.retained_view_entity.main_entity,
2889 auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER),
2890 subview_index: *face_index as u32,
2891 };
2892 shadow_map_visible_entities_query
2893 .get(*light_entity)
2894 .expect("Failed to get point light visible entities")
2895 .subviews
2896 .get(&retained_view_entity)
2897 .expect("Failed to get point light visible entity for face")
2898 }
2899 LightEntity::Spot { light_entity } => {
2900 let retained_view_entity = RetainedViewEntity {
2904 main_entity: extracted_view_light.retained_view_entity.main_entity,
2905 auxiliary_entity: MainEntity::from(Entity::PLACEHOLDER),
2906 subview_index: 0,
2907 };
2908 shadow_map_visible_entities_query
2909 .get(*light_entity)
2910 .expect("Failed to get spot light visible entities")
2911 .subviews
2912 .get(&retained_view_entity)
2913 .expect("Failed to get spot light visible entity for view")
2914 }
2915 }
2916}
2917
2918pub fn extract_shadow_lod_origin(
2925 global_transform_query: Extract<Query<&GlobalTransform>>,
2926 mut camera_query: Extract<Query<(Entity, &RenderTarget), With<Camera>>>,
2927 mut shadow_lod_origin_query: Extract<Query<Entity, With<ShadowLodOrigin>>>,
2928 mut lights_query: Extract<Query<Entity, Or<(With<PointLight>, With<SpotLight>)>>>,
2929 mut render_shadow_lod_origin: ResMut<RenderShadowLodOrigin>,
2930) {
2931 match bevy_light::get_shadow_lod_origin(
2932 camera_query.transmute_lens_filtered(),
2933 shadow_lod_origin_query.transmute_lens_filtered(),
2934 lights_query.transmute_lens_filtered(),
2935 )
2936 .and_then(|shadow_lod_origin_entity| global_transform_query.get(shadow_lod_origin_entity).ok())
2937 {
2938 Some(global_transform) => {
2939 render_shadow_lod_origin.0 = global_transform.translation();
2940 }
2941 None => render_shadow_lod_origin.0 = Default::default(),
2942 }
2943}