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