1use crate::*;
2use bevy_asset::UntypedAssetId;
3use bevy_camera::primitives::{
4 face_index_to_name, CascadesFrusta, CubeMapFace, CubemapFrusta, Frustum, HalfSpace,
5 CUBE_MAP_FACES,
6};
7use bevy_camera::visibility::{
8 CascadesVisibleEntities, CubemapVisibleEntities, RenderLayers, ViewVisibility,
9 VisibleMeshEntities,
10};
11use bevy_camera::Camera3d;
12use bevy_color::ColorToComponents;
13use bevy_core_pipeline::core_3d::CORE_3D_DEPTH_FORMAT;
14use bevy_derive::{Deref, DerefMut};
15use bevy_ecs::change_detection::Tick;
16use bevy_ecs::system::SystemChangeTick;
17use bevy_ecs::{
18 entity::{EntityHashMap, EntityHashSet},
19 prelude::*,
20 system::lifetimeless::Read,
21};
22use bevy_light::cascade::Cascade;
23use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType};
24use bevy_light::cluster::GlobalVisibleClusterableObjects;
25use bevy_light::SunDisk;
26use bevy_light::{
27 spot_light_clip_from_view, spot_light_world_from_view, AmbientLight, CascadeShadowConfig,
28 Cascades, DirectionalLight, DirectionalLightShadowMap, GlobalAmbientLight, NotShadowCaster,
29 PointLight, PointLightShadowMap, ShadowFilteringMethod, SpotLight, VolumetricLight,
30};
31use bevy_math::{ops, Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
32use bevy_platform::collections::{HashMap, HashSet};
33use bevy_platform::hash::FixedHasher;
34use bevy_render::erased_render_asset::ErasedRenderAssets;
35use bevy_render::experimental::occlusion_culling::{
36 OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
37};
38use bevy_render::sync_world::MainEntityHashMap;
39use bevy_render::{
40 batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
41 camera::SortedCameras,
42 mesh::allocator::MeshAllocator,
43 view::{NoIndirectDrawing, RetainedViewEntity},
44};
45use bevy_render::{
46 diagnostic::RecordDiagnostics,
47 mesh::RenderMesh,
48 render_asset::RenderAssets,
49 render_graph::{Node, NodeRunError, RenderGraphContext},
50 render_phase::*,
51 render_resource::*,
52 renderer::{RenderContext, RenderDevice, RenderQueue},
53 texture::*,
54 view::ExtractedView,
55 Extract,
56};
57use bevy_render::{
58 mesh::allocator::SlabId,
59 sync_world::{MainEntity, RenderEntity},
60};
61use bevy_transform::{components::GlobalTransform, prelude::Transform};
62use bevy_utils::default;
63use core::{hash::Hash, ops::Range};
64use decal::clustered::RenderClusteredDecals;
65#[cfg(feature = "trace")]
66use tracing::info_span;
67use tracing::{error, warn};
68
69#[derive(Component)]
70pub struct ExtractedPointLight {
71 pub color: LinearRgba,
72 pub intensity: f32,
74 pub range: f32,
75 pub radius: f32,
76 pub transform: GlobalTransform,
77 pub shadows_enabled: bool,
78 pub shadow_depth_bias: f32,
79 pub shadow_normal_bias: f32,
80 pub shadow_map_near_z: f32,
81 pub spot_light_angles: Option<(f32, f32)>,
82 pub volumetric: bool,
83 pub soft_shadows_enabled: bool,
84 pub affects_lightmapped_mesh_diffuse: bool,
86}
87
88#[derive(Component, Debug)]
89pub struct ExtractedDirectionalLight {
90 pub color: LinearRgba,
91 pub illuminance: f32,
92 pub transform: GlobalTransform,
93 pub shadows_enabled: bool,
94 pub volumetric: bool,
95 pub affects_lightmapped_mesh_diffuse: bool,
98 pub shadow_depth_bias: f32,
99 pub shadow_normal_bias: f32,
100 pub cascade_shadow_config: CascadeShadowConfig,
101 pub cascades: EntityHashMap<Vec<Cascade>>,
102 pub frusta: EntityHashMap<Vec<Frustum>>,
103 pub render_layers: RenderLayers,
104 pub soft_shadow_size: Option<f32>,
105 pub occlusion_culling: bool,
107 pub sun_disk_angular_size: f32,
108 pub sun_disk_intensity: f32,
109}
110
111bitflags::bitflags! {
113 #[repr(transparent)]
114 struct PointLightFlags: u32 {
115 const SHADOWS_ENABLED = 1 << 0;
116 const SPOT_LIGHT_Y_NEGATIVE = 1 << 1;
117 const VOLUMETRIC = 1 << 2;
118 const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1 << 3;
119 const NONE = 0;
120 const UNINITIALIZED = 0xFFFF;
121 }
122}
123
124#[derive(Copy, Clone, ShaderType, Default, Debug)]
125pub struct GpuDirectionalCascade {
126 clip_from_world: Mat4,
127 texel_size: f32,
128 far_bound: f32,
129}
130
131#[derive(Copy, Clone, ShaderType, Default, Debug)]
132pub struct GpuDirectionalLight {
133 cascades: [GpuDirectionalCascade; MAX_CASCADES_PER_LIGHT],
134 color: Vec4,
135 dir_to_light: Vec3,
136 flags: u32,
137 soft_shadow_size: f32,
138 shadow_depth_bias: f32,
139 shadow_normal_bias: f32,
140 num_cascades: u32,
141 cascades_overlap_proportion: f32,
142 depth_texture_base_index: u32,
143 decal_index: u32,
144 sun_disk_angular_size: f32,
145 sun_disk_intensity: f32,
146}
147
148bitflags::bitflags! {
150 #[repr(transparent)]
151 struct DirectionalLightFlags: u32 {
152 const SHADOWS_ENABLED = 1 << 0;
153 const VOLUMETRIC = 1 << 1;
154 const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1 << 2;
155 const NONE = 0;
156 const UNINITIALIZED = 0xFFFF;
157 }
158}
159
160#[derive(Copy, Clone, Debug, ShaderType)]
161pub struct GpuLights {
162 directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
163 ambient_color: Vec4,
164 cluster_dimensions: UVec4,
166 cluster_factors: Vec4,
170 n_directional_lights: u32,
171 spot_light_shadowmap_offset: i32,
173 ambient_light_affects_lightmapped_meshes: u32,
174}
175
176#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
179pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
180#[cfg(any(
181 not(feature = "webgl"),
182 not(target_arch = "wasm32"),
183 feature = "webgpu"
184))]
185pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
186#[cfg(any(
187 not(feature = "webgl"),
188 not(target_arch = "wasm32"),
189 feature = "webgpu"
190))]
191pub const MAX_CASCADES_PER_LIGHT: usize = 4;
192#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
193pub const MAX_CASCADES_PER_LIGHT: usize = 1;
194
195#[derive(Resource, Clone)]
196pub struct ShadowSamplers {
197 pub point_light_comparison_sampler: Sampler,
198 #[cfg(feature = "experimental_pbr_pcss")]
199 pub point_light_linear_sampler: Sampler,
200 pub directional_light_comparison_sampler: Sampler,
201 #[cfg(feature = "experimental_pbr_pcss")]
202 pub directional_light_linear_sampler: Sampler,
203}
204
205pub fn init_shadow_samplers(mut commands: Commands, render_device: Res<RenderDevice>) {
206 let base_sampler_descriptor = SamplerDescriptor {
207 address_mode_u: AddressMode::ClampToEdge,
208 address_mode_v: AddressMode::ClampToEdge,
209 address_mode_w: AddressMode::ClampToEdge,
210 mag_filter: FilterMode::Linear,
211 min_filter: FilterMode::Linear,
212 mipmap_filter: FilterMode::Nearest,
213 ..default()
214 };
215
216 commands.insert_resource(ShadowSamplers {
217 point_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
218 compare: Some(CompareFunction::GreaterEqual),
219 ..base_sampler_descriptor
220 }),
221 #[cfg(feature = "experimental_pbr_pcss")]
222 point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
223 directional_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
224 compare: Some(CompareFunction::GreaterEqual),
225 ..base_sampler_descriptor
226 }),
227 #[cfg(feature = "experimental_pbr_pcss")]
228 directional_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
229 });
230}
231
232pub fn extract_shadow_filtering_method(
235 mut commands: Commands,
236 mut previous_len: Local<usize>,
237 query: Extract<Query<(RenderEntity, &ShadowFilteringMethod)>>,
238) {
239 let mut values = Vec::with_capacity(*previous_len);
240 for (entity, query_item) in &query {
241 values.push((entity, *query_item));
242 }
243 *previous_len = values.len();
244 commands.try_insert_batch(values);
245}
246
247pub fn extract_ambient_light_resource(
250 mut commands: Commands,
251 main_resource: Extract<Option<Res<GlobalAmbientLight>>>,
252 target_resource: Option<ResMut<GlobalAmbientLight>>,
253) {
254 if let Some(main_resource) = main_resource.as_ref() {
255 if let Some(mut target_resource) = target_resource {
256 if main_resource.is_changed() {
257 *target_resource = (*main_resource).clone();
258 }
259 } else {
260 commands.insert_resource((*main_resource).clone());
261 }
262 }
263}
264
265pub fn extract_ambient_light(
268 mut commands: Commands,
269 mut previous_len: Local<usize>,
270 query: Extract<Query<(RenderEntity, &AmbientLight)>>,
271) {
272 let mut values = Vec::with_capacity(*previous_len);
273 for (entity, query_item) in &query {
274 values.push((entity, query_item.clone()));
275 }
276 *previous_len = values.len();
277 commands.try_insert_batch(values);
278}
279
280pub fn extract_lights(
281 mut commands: Commands,
282 point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
283 directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
284 global_visible_clusterable: Extract<Res<GlobalVisibleClusterableObjects>>,
285 previous_point_lights: Query<
286 Entity,
287 (
288 With<RenderCubemapVisibleEntities>,
289 With<ExtractedPointLight>,
290 ),
291 >,
292 previous_spot_lights: Query<
293 Entity,
294 (With<RenderVisibleMeshEntities>, With<ExtractedPointLight>),
295 >,
296 point_lights: Extract<
297 Query<(
298 Entity,
299 RenderEntity,
300 &PointLight,
301 &CubemapVisibleEntities,
302 &GlobalTransform,
303 &ViewVisibility,
304 &CubemapFrusta,
305 Option<&VolumetricLight>,
306 )>,
307 >,
308 spot_lights: Extract<
309 Query<(
310 Entity,
311 RenderEntity,
312 &SpotLight,
313 &VisibleMeshEntities,
314 &GlobalTransform,
315 &ViewVisibility,
316 &Frustum,
317 Option<&VolumetricLight>,
318 )>,
319 >,
320 directional_lights: Extract<
321 Query<
322 (
323 Entity,
324 RenderEntity,
325 &DirectionalLight,
326 &CascadesVisibleEntities,
327 &Cascades,
328 &CascadeShadowConfig,
329 &CascadesFrusta,
330 &GlobalTransform,
331 &ViewVisibility,
332 Option<&RenderLayers>,
333 Option<&VolumetricLight>,
334 Has<OcclusionCulling>,
335 Option<&SunDisk>,
336 ),
337 Without<SpotLight>,
338 >,
339 >,
340 mapper: Extract<Query<RenderEntity>>,
341 mut previous_point_lights_len: Local<usize>,
342 mut previous_spot_lights_len: Local<usize>,
343) {
344 if point_light_shadow_map.is_changed() {
347 commands.insert_resource(point_light_shadow_map.clone());
348 }
349 if directional_light_shadow_map.is_changed() {
350 commands.insert_resource(directional_light_shadow_map.clone());
351 }
352
353 commands.try_insert_batch(
356 previous_point_lights
357 .iter()
358 .map(|render_entity| (render_entity, RenderCubemapVisibleEntities::default()))
359 .collect::<Vec<_>>(),
360 );
361 commands.try_insert_batch(
362 previous_spot_lights
363 .iter()
364 .map(|render_entity| (render_entity, RenderVisibleMeshEntities::default()))
365 .collect::<Vec<_>>(),
366 );
367
368 let point_light_texel_size = 2.0 / point_light_shadow_map.size as f32;
376
377 let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
378 for entity in global_visible_clusterable.iter().copied() {
379 let Ok((
380 main_entity,
381 render_entity,
382 point_light,
383 cubemap_visible_entities,
384 transform,
385 view_visibility,
386 frusta,
387 volumetric_light,
388 )) = point_lights.get(entity)
389 else {
390 continue;
391 };
392 if !view_visibility.get() {
393 continue;
394 }
395 let render_cubemap_visible_entities = RenderCubemapVisibleEntities {
396 data: cubemap_visible_entities
397 .iter()
398 .map(|v| create_render_visible_mesh_entities(&mapper, v))
399 .collect::<Vec<_>>()
400 .try_into()
401 .unwrap(),
402 };
403
404 let extracted_point_light = ExtractedPointLight {
405 color: point_light.color.into(),
406 intensity: point_light.intensity / (4.0 * core::f32::consts::PI),
410 range: point_light.range,
411 radius: point_light.radius,
412 transform: *transform,
413 shadows_enabled: point_light.shadows_enabled,
414 shadow_depth_bias: point_light.shadow_depth_bias,
415 shadow_normal_bias: point_light.shadow_normal_bias
417 * point_light_texel_size
418 * core::f32::consts::SQRT_2,
419 shadow_map_near_z: point_light.shadow_map_near_z,
420 spot_light_angles: None,
421 volumetric: volumetric_light.is_some(),
422 affects_lightmapped_mesh_diffuse: point_light.affects_lightmapped_mesh_diffuse,
423 #[cfg(feature = "experimental_pbr_pcss")]
424 soft_shadows_enabled: point_light.soft_shadows_enabled,
425 #[cfg(not(feature = "experimental_pbr_pcss"))]
426 soft_shadows_enabled: false,
427 };
428 point_lights_values.push((
429 render_entity,
430 (
431 extracted_point_light,
432 render_cubemap_visible_entities,
433 (*frusta).clone(),
434 MainEntity::from(main_entity),
435 ),
436 ));
437 }
438 *previous_point_lights_len = point_lights_values.len();
439 commands.try_insert_batch(point_lights_values);
440
441 let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
442 for entity in global_visible_clusterable.iter().copied() {
443 if let Ok((
444 main_entity,
445 render_entity,
446 spot_light,
447 visible_entities,
448 transform,
449 view_visibility,
450 frustum,
451 volumetric_light,
452 )) = spot_lights.get(entity)
453 {
454 if !view_visibility.get() {
455 continue;
456 }
457 let render_visible_entities =
458 create_render_visible_mesh_entities(&mapper, visible_entities);
459
460 let texel_size =
461 2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32;
462
463 spot_lights_values.push((
464 render_entity,
465 (
466 ExtractedPointLight {
467 color: spot_light.color.into(),
468 intensity: spot_light.intensity / (4.0 * core::f32::consts::PI),
475 range: spot_light.range,
476 radius: spot_light.radius,
477 transform: *transform,
478 shadows_enabled: spot_light.shadows_enabled,
479 shadow_depth_bias: spot_light.shadow_depth_bias,
480 shadow_normal_bias: spot_light.shadow_normal_bias
482 * texel_size
483 * core::f32::consts::SQRT_2,
484 shadow_map_near_z: spot_light.shadow_map_near_z,
485 spot_light_angles: Some((spot_light.inner_angle, spot_light.outer_angle)),
486 volumetric: volumetric_light.is_some(),
487 affects_lightmapped_mesh_diffuse: spot_light
488 .affects_lightmapped_mesh_diffuse,
489 #[cfg(feature = "experimental_pbr_pcss")]
490 soft_shadows_enabled: spot_light.soft_shadows_enabled,
491 #[cfg(not(feature = "experimental_pbr_pcss"))]
492 soft_shadows_enabled: false,
493 },
494 render_visible_entities,
495 *frustum,
496 MainEntity::from(main_entity),
497 ),
498 ));
499 }
500 }
501 *previous_spot_lights_len = spot_lights_values.len();
502 commands.try_insert_batch(spot_lights_values);
503
504 for (
505 main_entity,
506 entity,
507 directional_light,
508 visible_entities,
509 cascades,
510 cascade_config,
511 frusta,
512 transform,
513 view_visibility,
514 maybe_layers,
515 volumetric_light,
516 occlusion_culling,
517 sun_disk,
518 ) in &directional_lights
519 {
520 if !view_visibility.get() {
521 commands
522 .get_entity(entity)
523 .expect("Light entity wasn't synced.")
524 .remove::<(ExtractedDirectionalLight, RenderCascadesVisibleEntities)>();
525 continue;
526 }
527
528 let mut extracted_cascades = EntityHashMap::default();
530 let mut extracted_frusta = EntityHashMap::default();
531 let mut cascade_visible_entities = EntityHashMap::default();
532 for (e, v) in cascades.cascades.iter() {
533 if let Ok(entity) = mapper.get(*e) {
534 extracted_cascades.insert(entity, v.clone());
535 } else {
536 break;
537 }
538 }
539 for (e, v) in frusta.frusta.iter() {
540 if let Ok(entity) = mapper.get(*e) {
541 extracted_frusta.insert(entity, v.clone());
542 } else {
543 break;
544 }
545 }
546 for (e, v) in visible_entities.entities.iter() {
547 if let Ok(entity) = mapper.get(*e) {
548 cascade_visible_entities.insert(
549 entity,
550 v.iter()
551 .map(|v| create_render_visible_mesh_entities(&mapper, v))
552 .collect(),
553 );
554 } else {
555 break;
556 }
557 }
558
559 commands
560 .get_entity(entity)
561 .expect("Light entity wasn't synced.")
562 .insert((
563 ExtractedDirectionalLight {
564 color: directional_light.color.into(),
565 illuminance: directional_light.illuminance,
566 transform: *transform,
567 volumetric: volumetric_light.is_some(),
568 affects_lightmapped_mesh_diffuse: directional_light
569 .affects_lightmapped_mesh_diffuse,
570 #[cfg(feature = "experimental_pbr_pcss")]
571 soft_shadow_size: directional_light.soft_shadow_size,
572 #[cfg(not(feature = "experimental_pbr_pcss"))]
573 soft_shadow_size: None,
574 shadows_enabled: directional_light.shadows_enabled,
575 shadow_depth_bias: directional_light.shadow_depth_bias,
576 shadow_normal_bias: directional_light.shadow_normal_bias
578 * core::f32::consts::SQRT_2,
579 cascade_shadow_config: cascade_config.clone(),
580 cascades: extracted_cascades,
581 frusta: extracted_frusta,
582 render_layers: maybe_layers.unwrap_or_default().clone(),
583 occlusion_culling,
584 sun_disk_angular_size: sun_disk.unwrap_or_default().angular_size,
585 sun_disk_intensity: sun_disk.unwrap_or_default().intensity,
586 },
587 RenderCascadesVisibleEntities {
588 entities: cascade_visible_entities,
589 },
590 MainEntity::from(main_entity),
591 ));
592 }
593}
594
595fn create_render_visible_mesh_entities(
596 mapper: &Extract<Query<RenderEntity>>,
597 visible_entities: &VisibleMeshEntities,
598) -> RenderVisibleMeshEntities {
599 RenderVisibleMeshEntities {
600 entities: visible_entities
601 .iter()
602 .map(|e| {
603 let render_entity = mapper.get(*e).unwrap_or(Entity::PLACEHOLDER);
604 (render_entity, MainEntity::from(*e))
605 })
606 .collect(),
607 }
608}
609
610#[derive(Component, Default, Deref, DerefMut)]
611pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
614
615pub(crate) fn add_light_view_entities(
617 add: On<Add, (ExtractedDirectionalLight, ExtractedPointLight)>,
618 mut commands: Commands,
619) {
620 if let Ok(mut v) = commands.get_entity(add.entity) {
621 v.insert(LightViewEntities::default());
622 }
623}
624
625pub(crate) fn extracted_light_removed(
627 remove: On<Remove, (ExtractedDirectionalLight, ExtractedPointLight)>,
628 mut commands: Commands,
629) {
630 if let Ok(mut v) = commands.get_entity(remove.entity) {
631 v.try_remove::<LightViewEntities>();
632 }
633}
634
635pub(crate) fn remove_light_view_entities(
636 remove: On<Remove, LightViewEntities>,
637 query: Query<&LightViewEntities>,
638 mut commands: Commands,
639) {
640 if let Ok(entities) = query.get(remove.entity) {
641 for v in entities.0.values() {
642 for e in v.iter().copied() {
643 if let Ok(mut v) = commands.get_entity(e) {
644 v.despawn();
645 }
646 }
647 }
648 }
649}
650
651#[derive(Component)]
652pub struct ShadowView {
653 pub depth_attachment: DepthAttachment,
654 pub pass_name: String,
655}
656
657#[derive(Component)]
658pub struct ViewShadowBindings {
659 pub point_light_depth_texture: Texture,
660 pub point_light_depth_texture_view: TextureView,
661 pub directional_light_depth_texture: Texture,
662 pub directional_light_depth_texture_view: TextureView,
663}
664
665#[derive(Component)]
671pub struct ViewLightEntities {
672 pub lights: Vec<Entity>,
678}
679
680#[derive(Component)]
681pub struct ViewLightsUniformOffset {
682 pub offset: u32,
683}
684
685#[derive(Resource, Default)]
686pub struct LightMeta {
687 pub view_gpu_lights: DynamicUniformBuffer<GpuLights>,
688}
689
690#[derive(Component)]
691pub enum LightEntity {
692 Directional {
693 light_entity: Entity,
694 cascade_index: usize,
695 },
696 Point {
697 light_entity: Entity,
698 face_index: usize,
699 },
700 Spot {
701 light_entity: Entity,
702 },
703}
704
705pub fn prepare_lights(
706 mut commands: Commands,
707 mut texture_cache: ResMut<TextureCache>,
708 (render_device, render_queue): (Res<RenderDevice>, Res<RenderQueue>),
709 mut global_light_meta: ResMut<GlobalClusterableObjectMeta>,
710 mut light_meta: ResMut<LightMeta>,
711 views: Query<
712 (
713 Entity,
714 MainEntity,
715 &ExtractedView,
716 &ExtractedClusterConfig,
717 Option<&RenderLayers>,
718 Has<NoIndirectDrawing>,
719 Option<&AmbientLight>,
720 ),
721 With<Camera3d>,
722 >,
723 ambient_light: Res<GlobalAmbientLight>,
724 point_light_shadow_map: Res<PointLightShadowMap>,
725 directional_light_shadow_map: Res<DirectionalLightShadowMap>,
726 mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
727 (
728 mut max_directional_lights_warning_emitted,
729 mut max_cascades_per_light_warning_emitted,
730 mut live_shadow_mapping_lights,
731 ): (Local<bool>, Local<bool>, Local<HashSet<RetainedViewEntity>>),
732 point_lights: Query<(
733 Entity,
734 &MainEntity,
735 &ExtractedPointLight,
736 AnyOf<(&CubemapFrusta, &Frustum)>,
737 )>,
738 directional_lights: Query<(Entity, &MainEntity, &ExtractedDirectionalLight)>,
739 mut light_view_entities: Query<&mut LightViewEntities>,
740 sorted_cameras: Res<SortedCameras>,
741 (gpu_preprocessing_support, decals): (
742 Res<GpuPreprocessingSupport>,
743 Option<Res<RenderClusteredDecals>>,
744 ),
745) {
746 let views_iter = views.iter();
747 let views_count = views_iter.len();
748 let Some(mut view_gpu_lights_writer) =
749 light_meta
750 .view_gpu_lights
751 .get_writer(views_count, &render_device, &render_queue)
752 else {
753 return;
754 };
755
756 let cube_face_rotations = CUBE_MAP_FACES
758 .iter()
759 .map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
760 .collect::<Vec<_>>();
761
762 global_light_meta.entity_to_index.clear();
763
764 let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
765 let mut directional_lights: Vec<_> = directional_lights.iter().collect::<Vec<_>>();
766
767 #[cfg(any(
768 not(feature = "webgl"),
769 not(target_arch = "wasm32"),
770 feature = "webgpu"
771 ))]
772 let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize;
773 #[cfg(any(
774 not(feature = "webgl"),
775 not(target_arch = "wasm32"),
776 feature = "webgpu"
777 ))]
778 let max_texture_cubes = max_texture_array_layers / 6;
779 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
780 let max_texture_array_layers = 1;
781 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
782 let max_texture_cubes = 1;
783
784 if !*max_directional_lights_warning_emitted && directional_lights.len() > MAX_DIRECTIONAL_LIGHTS
785 {
786 warn!(
787 "The amount of directional lights of {} is exceeding the supported limit of {}.",
788 directional_lights.len(),
789 MAX_DIRECTIONAL_LIGHTS
790 );
791 *max_directional_lights_warning_emitted = true;
792 }
793
794 if !*max_cascades_per_light_warning_emitted
795 && directional_lights
796 .iter()
797 .any(|(_, _, light)| light.cascade_shadow_config.bounds.len() > MAX_CASCADES_PER_LIGHT)
798 {
799 warn!(
800 "The number of cascades configured for a directional light exceeds the supported limit of {}.",
801 MAX_CASCADES_PER_LIGHT
802 );
803 *max_cascades_per_light_warning_emitted = true;
804 }
805
806 let point_light_count = point_lights
807 .iter()
808 .filter(|light| light.2.spot_light_angles.is_none())
809 .count();
810
811 let point_light_volumetric_enabled_count = point_lights
812 .iter()
813 .filter(|(_, _, light, _)| light.volumetric && light.spot_light_angles.is_none())
814 .count()
815 .min(max_texture_cubes);
816
817 let point_light_shadow_maps_count = point_lights
818 .iter()
819 .filter(|light| light.2.shadows_enabled && light.2.spot_light_angles.is_none())
820 .count()
821 .min(max_texture_cubes);
822
823 let directional_volumetric_enabled_count = directional_lights
824 .iter()
825 .take(MAX_DIRECTIONAL_LIGHTS)
826 .filter(|(_, _, light)| light.volumetric)
827 .count()
828 .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
829
830 let directional_shadow_enabled_count = directional_lights
831 .iter()
832 .take(MAX_DIRECTIONAL_LIGHTS)
833 .filter(|(_, _, light)| light.shadows_enabled)
834 .count()
835 .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
836
837 let spot_light_count = point_lights
838 .iter()
839 .filter(|(_, _, light, _)| light.spot_light_angles.is_some())
840 .count()
841 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
842
843 let spot_light_volumetric_enabled_count = point_lights
844 .iter()
845 .filter(|(_, _, light, _)| light.volumetric && light.spot_light_angles.is_some())
846 .count()
847 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
848
849 let spot_light_shadow_maps_count = point_lights
850 .iter()
851 .filter(|(_, _, light, _)| light.shadows_enabled && light.spot_light_angles.is_some())
852 .count()
853 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
854
855 point_lights.sort_by_cached_key(|(entity, _, light, _)| {
861 (
862 point_or_spot_light_to_clusterable(light).ordering(),
863 *entity,
864 )
865 });
866
867 directional_lights.sort_unstable_by_key(|(entity, _, light)| {
878 (light.volumetric, light.shadows_enabled, *entity)
879 });
880
881 if global_light_meta.entity_to_index.capacity() < point_lights.len() {
882 global_light_meta
883 .entity_to_index
884 .reserve(point_lights.len());
885 }
886
887 let mut gpu_point_lights = Vec::new();
888 for (index, &(entity, _, light, _)) in point_lights.iter().enumerate() {
889 let mut flags = PointLightFlags::NONE;
890
891 if light.shadows_enabled
893 && (index < point_light_shadow_maps_count
894 || (light.spot_light_angles.is_some()
895 && index - point_light_count < spot_light_shadow_maps_count))
896 {
897 flags |= PointLightFlags::SHADOWS_ENABLED;
898 }
899
900 let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
901 core::f32::consts::FRAC_PI_2,
902 1.0,
903 light.shadow_map_near_z,
904 );
905 if light.shadows_enabled
906 && light.volumetric
907 && (index < point_light_volumetric_enabled_count
908 || (light.spot_light_angles.is_some()
909 && index - point_light_count < spot_light_volumetric_enabled_count))
910 {
911 flags |= PointLightFlags::VOLUMETRIC;
912 }
913
914 if light.affects_lightmapped_mesh_diffuse {
915 flags |= PointLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
916 }
917
918 let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles {
919 Some((inner, outer)) => {
920 let light_direction = light.transform.forward();
921 if light_direction.y.is_sign_negative() {
922 flags |= PointLightFlags::SPOT_LIGHT_Y_NEGATIVE;
923 }
924
925 let cos_outer = ops::cos(outer);
926 let spot_scale = 1.0 / f32::max(ops::cos(inner) - cos_outer, 1e-4);
927 let spot_offset = -cos_outer * spot_scale;
928
929 (
930 light_direction.xz().extend(spot_scale).extend(spot_offset),
932 ops::tan(outer),
933 )
934 }
935 None => {
936 (
937 Vec4::new(
939 cube_face_projection.z_axis.z,
940 cube_face_projection.z_axis.w,
941 cube_face_projection.w_axis.z,
942 cube_face_projection.w_axis.w,
943 ),
944 0.0,
946 )
947 }
948 };
949
950 gpu_point_lights.push(GpuClusterableObject {
951 light_custom_data,
952 color_inverse_square_range: (Vec4::from_slice(&light.color.to_f32_array())
955 * light.intensity)
956 .xyz()
957 .extend(1.0 / (light.range * light.range)),
958 position_radius: light.transform.translation().extend(light.radius),
959 flags: flags.bits(),
960 shadow_depth_bias: light.shadow_depth_bias,
961 shadow_normal_bias: light.shadow_normal_bias,
962 shadow_map_near_z: light.shadow_map_near_z,
963 spot_light_tan_angle,
964 decal_index: decals
965 .as_ref()
966 .and_then(|decals| decals.get(entity))
967 .and_then(|index| index.try_into().ok())
968 .unwrap_or(u32::MAX),
969 pad: 0.0,
970 soft_shadow_size: if light.soft_shadows_enabled {
971 light.radius
972 } else {
973 0.0
974 },
975 });
976 global_light_meta.entity_to_index.insert(entity, index);
977 }
978
979 let mut num_directional_cascades_enabled = 0usize;
981 for (
982 _entity,
983 _camera_main_entity,
984 _extracted_view,
985 _clusters,
986 maybe_layers,
987 _no_indirect_drawing,
988 _maybe_ambient_override,
989 ) in sorted_cameras
990 .0
991 .iter()
992 .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
993 {
994 let mut num_directional_cascades_for_this_view = 0usize;
995 let render_layers = maybe_layers.unwrap_or_default();
996
997 for (_light_entity, _, light) in directional_lights.iter() {
998 if light.shadows_enabled && light.render_layers.intersects(render_layers) {
999 num_directional_cascades_for_this_view += light
1000 .cascade_shadow_config
1001 .bounds
1002 .len()
1003 .min(MAX_CASCADES_PER_LIGHT);
1004 }
1005 }
1006
1007 num_directional_cascades_enabled = num_directional_cascades_enabled
1008 .max(num_directional_cascades_for_this_view)
1009 .min(max_texture_array_layers);
1010 }
1011
1012 global_light_meta
1013 .gpu_clusterable_objects
1014 .set(gpu_point_lights);
1015 global_light_meta
1016 .gpu_clusterable_objects
1017 .write_buffer(&render_device, &render_queue);
1018
1019 live_shadow_mapping_lights.clear();
1020
1021 let mut point_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1022 let mut directional_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1023
1024 let point_light_depth_texture = texture_cache.get(
1025 &render_device,
1026 TextureDescriptor {
1027 size: Extent3d {
1028 width: point_light_shadow_map.size as u32,
1029 height: point_light_shadow_map.size as u32,
1030 depth_or_array_layers: point_light_shadow_maps_count.max(1) as u32 * 6,
1031 },
1032 mip_level_count: 1,
1033 sample_count: 1,
1034 dimension: TextureDimension::D2,
1035 format: CORE_3D_DEPTH_FORMAT,
1036 label: Some("point_light_shadow_map_texture"),
1037 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1038 view_formats: &[],
1039 },
1040 );
1041
1042 let point_light_depth_texture_view =
1043 point_light_depth_texture
1044 .texture
1045 .create_view(&TextureViewDescriptor {
1046 label: Some("point_light_shadow_map_array_texture_view"),
1047 format: None,
1048 #[cfg(all(
1051 not(target_abi = "sim"),
1052 any(
1053 not(feature = "webgl"),
1054 not(target_arch = "wasm32"),
1055 feature = "webgpu"
1056 )
1057 ))]
1058 dimension: Some(TextureViewDimension::CubeArray),
1059 #[cfg(any(
1060 target_abi = "sim",
1061 all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
1062 ))]
1063 dimension: Some(TextureViewDimension::Cube),
1064 usage: None,
1065 aspect: TextureAspect::DepthOnly,
1066 base_mip_level: 0,
1067 mip_level_count: None,
1068 base_array_layer: 0,
1069 array_layer_count: None,
1070 });
1071
1072 let directional_light_depth_texture = texture_cache.get(
1073 &render_device,
1074 TextureDescriptor {
1075 size: Extent3d {
1076 width: (directional_light_shadow_map.size as u32)
1077 .min(render_device.limits().max_texture_dimension_2d),
1078 height: (directional_light_shadow_map.size as u32)
1079 .min(render_device.limits().max_texture_dimension_2d),
1080 depth_or_array_layers: (num_directional_cascades_enabled
1081 + spot_light_shadow_maps_count)
1082 .max(1) as u32,
1083 },
1084 mip_level_count: 1,
1085 sample_count: 1,
1086 dimension: TextureDimension::D2,
1087 format: CORE_3D_DEPTH_FORMAT,
1088 label: Some("directional_light_shadow_map_texture"),
1089 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1090 view_formats: &[],
1091 },
1092 );
1093
1094 let directional_light_depth_texture_view =
1095 directional_light_depth_texture
1096 .texture
1097 .create_view(&TextureViewDescriptor {
1098 label: Some("directional_light_shadow_map_array_texture_view"),
1099 format: None,
1100 #[cfg(any(
1101 not(feature = "webgl"),
1102 not(target_arch = "wasm32"),
1103 feature = "webgpu"
1104 ))]
1105 dimension: Some(TextureViewDimension::D2Array),
1106 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1107 dimension: Some(TextureViewDimension::D2),
1108 usage: None,
1109 aspect: TextureAspect::DepthOnly,
1110 base_mip_level: 0,
1111 mip_level_count: None,
1112 base_array_layer: 0,
1113 array_layer_count: None,
1114 });
1115
1116 let mut live_views = EntityHashSet::with_capacity(views_count);
1117
1118 for (
1120 entity,
1121 camera_main_entity,
1122 extracted_view,
1123 clusters,
1124 maybe_layers,
1125 no_indirect_drawing,
1126 maybe_ambient_override,
1127 ) in sorted_cameras
1128 .0
1129 .iter()
1130 .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
1131 {
1132 live_views.insert(entity);
1133
1134 let view_layers = maybe_layers.unwrap_or_default();
1135 let mut view_lights = Vec::new();
1136 let mut view_occlusion_culling_lights = Vec::new();
1137
1138 let gpu_preprocessing_mode = gpu_preprocessing_support.min(if !no_indirect_drawing {
1139 GpuPreprocessingMode::Culling
1140 } else {
1141 GpuPreprocessingMode::PreprocessingOnly
1142 });
1143
1144 let is_orthographic = extracted_view.clip_from_view.w_axis.w == 1.0;
1145 let cluster_factors_zw = calculate_cluster_factors(
1146 clusters.near,
1147 clusters.far,
1148 clusters.dimensions.z as f32,
1149 is_orthographic,
1150 );
1151
1152 let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
1153 let ambient_light = AmbientLight {
1154 color: ambient_light.color,
1155 brightness: ambient_light.brightness,
1156 affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes,
1157 };
1158 let ambient_light = maybe_ambient_override.unwrap_or(&ambient_light);
1159
1160 let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
1161 let mut num_directional_cascades_enabled_for_this_view = 0usize;
1162 let mut num_directional_lights_for_this_view = 0usize;
1163 for (index, (light_entity, _, light)) in directional_lights
1164 .iter()
1165 .filter(|(_light_entity, _, light)| light.render_layers.intersects(view_layers))
1166 .enumerate()
1167 .take(MAX_DIRECTIONAL_LIGHTS)
1168 {
1169 num_directional_lights_for_this_view += 1;
1170
1171 let mut flags = DirectionalLightFlags::NONE;
1172
1173 if light.volumetric
1175 && light.shadows_enabled
1176 && (index < directional_volumetric_enabled_count)
1177 {
1178 flags |= DirectionalLightFlags::VOLUMETRIC;
1179 }
1180
1181 let mut num_cascades = 0;
1183 if light.shadows_enabled {
1184 let cascades = light
1185 .cascade_shadow_config
1186 .bounds
1187 .len()
1188 .min(MAX_CASCADES_PER_LIGHT);
1189
1190 if num_directional_cascades_enabled_for_this_view + cascades
1191 <= max_texture_array_layers
1192 {
1193 flags |= DirectionalLightFlags::SHADOWS_ENABLED;
1194 num_cascades += cascades;
1195 }
1196 }
1197
1198 if light.affects_lightmapped_mesh_diffuse {
1199 flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1200 }
1201
1202 gpu_directional_lights[index] = GpuDirectionalLight {
1203 cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1205 color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1208 dir_to_light: light.transform.back().into(),
1210 flags: flags.bits(),
1211 soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
1212 shadow_depth_bias: light.shadow_depth_bias,
1213 shadow_normal_bias: light.shadow_normal_bias,
1214 num_cascades: num_cascades as u32,
1215 cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
1216 depth_texture_base_index: num_directional_cascades_enabled_for_this_view as u32,
1217 sun_disk_angular_size: light.sun_disk_angular_size,
1218 sun_disk_intensity: light.sun_disk_intensity,
1219 decal_index: decals
1220 .as_ref()
1221 .and_then(|decals| decals.get(*light_entity))
1222 .and_then(|index| index.try_into().ok())
1223 .unwrap_or(u32::MAX),
1224 };
1225 num_directional_cascades_enabled_for_this_view += num_cascades;
1226 }
1227
1228 let mut gpu_lights = GpuLights {
1229 directional_lights: gpu_directional_lights,
1230 ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
1231 * ambient_light.brightness,
1232 cluster_factors: Vec4::new(
1233 clusters.dimensions.x as f32 / extracted_view.viewport.z as f32,
1234 clusters.dimensions.y as f32 / extracted_view.viewport.w as f32,
1235 cluster_factors_zw.x,
1236 cluster_factors_zw.y,
1237 ),
1238 cluster_dimensions: clusters.dimensions.extend(n_clusters),
1239 n_directional_lights: num_directional_lights_for_this_view as u32,
1240 spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
1244 - point_light_count as i32,
1245 ambient_light_affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes
1246 as u32,
1247 };
1248
1249 for &(light_entity, light_main_entity, light, (point_light_frusta, _)) in point_lights
1251 .iter()
1252 .take(point_light_count.min(max_texture_cubes))
1254 {
1255 let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1256 continue;
1257 };
1258
1259 if !light.shadows_enabled {
1260 if let Some(entities) = light_view_entities.remove(&entity) {
1261 despawn_entities(&mut commands, entities);
1262 }
1263 continue;
1264 }
1265
1266 let light_index = *global_light_meta
1267 .entity_to_index
1268 .get(&light_entity)
1269 .unwrap();
1270 let view_translation = GlobalTransform::from_translation(light.transform.translation());
1274
1275 let light_view_entities = light_view_entities
1277 .entry(entity)
1278 .or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect());
1279
1280 let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
1281 core::f32::consts::FRAC_PI_2,
1282 1.0,
1283 light.shadow_map_near_z,
1284 );
1285
1286 for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
1287 .iter()
1288 .zip(&point_light_frusta.unwrap().frusta)
1289 .zip(light_view_entities.iter().copied())
1290 .enumerate()
1291 {
1292 let mut first = false;
1293 let base_array_layer = (light_index * 6 + face_index) as u32;
1294
1295 let depth_attachment = point_light_depth_attachments
1296 .entry(base_array_layer)
1297 .or_insert_with(|| {
1298 first = true;
1299
1300 let depth_texture_view =
1301 point_light_depth_texture
1302 .texture
1303 .create_view(&TextureViewDescriptor {
1304 label: Some("point_light_shadow_map_texture_view"),
1305 format: None,
1306 dimension: Some(TextureViewDimension::D2),
1307 usage: None,
1308 aspect: TextureAspect::All,
1309 base_mip_level: 0,
1310 mip_level_count: None,
1311 base_array_layer,
1312 array_layer_count: Some(1u32),
1313 });
1314
1315 DepthAttachment::new(depth_texture_view, Some(0.0))
1316 })
1317 .clone();
1318
1319 let retained_view_entity = RetainedViewEntity::new(
1320 *light_main_entity,
1321 Some(camera_main_entity.into()),
1322 face_index as u32,
1323 );
1324
1325 commands.entity(view_light_entity).insert((
1326 ShadowView {
1327 depth_attachment,
1328 pass_name: format!(
1329 "shadow_point_light_{}_{}",
1330 light_index,
1331 face_index_to_name(face_index)
1332 ),
1333 },
1334 ExtractedView {
1335 retained_view_entity,
1336 viewport: UVec4::new(
1337 0,
1338 0,
1339 point_light_shadow_map.size as u32,
1340 point_light_shadow_map.size as u32,
1341 ),
1342 world_from_view: view_translation * *view_rotation,
1343 clip_from_world: None,
1344 clip_from_view: cube_face_projection,
1345 hdr: false,
1346 color_grading: Default::default(),
1347 invert_culling: false,
1348 },
1349 *frustum,
1350 LightEntity::Point {
1351 light_entity,
1352 face_index,
1353 },
1354 ));
1355
1356 if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1357 commands.entity(view_light_entity).insert(NoIndirectDrawing);
1358 }
1359
1360 view_lights.push(view_light_entity);
1361
1362 if first {
1363 shadow_render_phases
1365 .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1366 live_shadow_mapping_lights.insert(retained_view_entity);
1367 }
1368 }
1369 }
1370
1371 for (light_index, &(light_entity, light_main_entity, light, (_, spot_light_frustum))) in
1373 point_lights
1374 .iter()
1375 .skip(point_light_count)
1376 .take(spot_light_count)
1377 .enumerate()
1378 {
1379 let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1380 continue;
1381 };
1382
1383 if !light.shadows_enabled {
1384 if let Some(entities) = light_view_entities.remove(&entity) {
1385 despawn_entities(&mut commands, entities);
1386 }
1387 continue;
1388 }
1389
1390 let spot_world_from_view = spot_light_world_from_view(&light.transform);
1391 let spot_world_from_view = spot_world_from_view.into();
1392
1393 let angle = light.spot_light_angles.expect("lights should be sorted so that \
1394 [point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
1395 let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
1396
1397 let mut first = false;
1398 let base_array_layer = (num_directional_cascades_enabled + light_index) as u32;
1399
1400 let depth_attachment = directional_light_depth_attachments
1401 .entry(base_array_layer)
1402 .or_insert_with(|| {
1403 first = true;
1404
1405 let depth_texture_view = directional_light_depth_texture.texture.create_view(
1406 &TextureViewDescriptor {
1407 label: Some("spot_light_shadow_map_texture_view"),
1408 format: None,
1409 dimension: Some(TextureViewDimension::D2),
1410 usage: None,
1411 aspect: TextureAspect::All,
1412 base_mip_level: 0,
1413 mip_level_count: None,
1414 base_array_layer,
1415 array_layer_count: Some(1u32),
1416 },
1417 );
1418
1419 DepthAttachment::new(depth_texture_view, Some(0.0))
1420 })
1421 .clone();
1422
1423 let light_view_entities = light_view_entities
1424 .entry(entity)
1425 .or_insert_with(|| vec![commands.spawn_empty().id()]);
1426
1427 let view_light_entity = light_view_entities[0];
1428
1429 let retained_view_entity =
1430 RetainedViewEntity::new(*light_main_entity, Some(camera_main_entity.into()), 0);
1431
1432 commands.entity(view_light_entity).insert((
1433 ShadowView {
1434 depth_attachment,
1435 pass_name: format!("shadow_spot_light_{light_index}"),
1436 },
1437 ExtractedView {
1438 retained_view_entity,
1439 viewport: UVec4::new(
1440 0,
1441 0,
1442 directional_light_shadow_map.size as u32,
1443 directional_light_shadow_map.size as u32,
1444 ),
1445 world_from_view: spot_world_from_view,
1446 clip_from_view: spot_projection,
1447 clip_from_world: None,
1448 hdr: false,
1449 color_grading: Default::default(),
1450 invert_culling: false,
1451 },
1452 *spot_light_frustum.unwrap(),
1453 LightEntity::Spot { light_entity },
1454 ));
1455
1456 if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1457 commands.entity(view_light_entity).insert(NoIndirectDrawing);
1458 }
1459
1460 view_lights.push(view_light_entity);
1461
1462 if first {
1463 shadow_render_phases
1465 .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1466 live_shadow_mapping_lights.insert(retained_view_entity);
1467 }
1468 }
1469
1470 for &(light_entity, _, _) in directional_lights
1473 .iter()
1474 .filter(|(_, _, light)| !light.render_layers.intersects(view_layers))
1475 {
1476 let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1477 continue;
1478 };
1479 if let Some(entities) = light_view_entities.remove(&entity) {
1480 despawn_entities(&mut commands, entities);
1481 }
1482 }
1483
1484 let mut directional_depth_texture_array_index = 0u32;
1485 for (light_index, &(light_entity, light_main_entity, light)) in directional_lights
1486 .iter()
1487 .filter(|(_, _, light)| light.render_layers.intersects(view_layers))
1488 .enumerate()
1489 .take(MAX_DIRECTIONAL_LIGHTS)
1490 {
1491 let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1492 continue;
1493 };
1494
1495 let gpu_light = &mut gpu_lights.directional_lights[light_index];
1496
1497 if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
1499 if let Some(entities) = light_view_entities.remove(&entity) {
1500 despawn_entities(&mut commands, entities);
1501 }
1502 continue;
1503 }
1504
1505 let cascades = light
1506 .cascades
1507 .get(&entity)
1508 .unwrap()
1509 .iter()
1510 .take(MAX_CASCADES_PER_LIGHT);
1511 let frusta = light
1512 .frusta
1513 .get(&entity)
1514 .unwrap()
1515 .iter()
1516 .take(MAX_CASCADES_PER_LIGHT);
1517
1518 let iter = cascades
1519 .zip(frusta)
1520 .zip(&light.cascade_shadow_config.bounds);
1521
1522 let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
1523 (0..iter.len())
1524 .map(|_| commands.spawn_empty().id())
1525 .collect()
1526 });
1527 if light_view_entities.len() != iter.len() {
1528 let entities = core::mem::take(light_view_entities);
1529 despawn_entities(&mut commands, entities);
1530 light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
1531 }
1532
1533 for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
1534 iter.zip(light_view_entities.iter().copied()).enumerate()
1535 {
1536 gpu_lights.directional_lights[light_index].cascades[cascade_index] =
1537 GpuDirectionalCascade {
1538 clip_from_world: cascade.clip_from_world,
1539 texel_size: cascade.texel_size,
1540 far_bound: *bound,
1541 };
1542
1543 let depth_texture_view =
1544 directional_light_depth_texture
1545 .texture
1546 .create_view(&TextureViewDescriptor {
1547 label: Some("directional_light_shadow_map_array_texture_view"),
1548 format: None,
1549 dimension: Some(TextureViewDimension::D2),
1550 usage: None,
1551 aspect: TextureAspect::All,
1552 base_mip_level: 0,
1553 mip_level_count: None,
1554 base_array_layer: directional_depth_texture_array_index,
1555 array_layer_count: Some(1u32),
1556 });
1557
1558 let depth_attachment = DepthAttachment::new(depth_texture_view.clone(), Some(0.0));
1562
1563 directional_depth_texture_array_index += 1;
1564
1565 let mut frustum = *frustum;
1566 frustum.half_spaces[Frustum::NEAR_PLANE_IDX] = HalfSpace::new(
1568 frustum.half_spaces[Frustum::NEAR_PLANE_IDX]
1569 .normal()
1570 .extend(f32::INFINITY),
1571 );
1572
1573 let retained_view_entity = RetainedViewEntity::new(
1574 *light_main_entity,
1575 Some(camera_main_entity.into()),
1576 cascade_index as u32,
1577 );
1578
1579 commands.entity(view_light_entity).insert((
1580 ShadowView {
1581 depth_attachment,
1582 pass_name: format!(
1583 "shadow_directional_light_{light_index}_cascade_{cascade_index}"
1584 ),
1585 },
1586 ExtractedView {
1587 retained_view_entity,
1588 viewport: UVec4::new(
1589 0,
1590 0,
1591 directional_light_shadow_map.size as u32,
1592 directional_light_shadow_map.size as u32,
1593 ),
1594 world_from_view: GlobalTransform::from(cascade.world_from_cascade),
1595 clip_from_view: cascade.clip_from_cascade,
1596 clip_from_world: Some(cascade.clip_from_world),
1597 hdr: false,
1598 color_grading: Default::default(),
1599 invert_culling: false,
1600 },
1601 frustum,
1602 LightEntity::Directional {
1603 light_entity,
1604 cascade_index,
1605 },
1606 ));
1607
1608 if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1609 commands.entity(view_light_entity).insert(NoIndirectDrawing);
1610 }
1611
1612 view_lights.push(view_light_entity);
1613
1614 if light.occlusion_culling {
1616 commands.entity(view_light_entity).insert((
1617 OcclusionCulling,
1618 OcclusionCullingSubview {
1619 depth_texture_view,
1620 depth_texture_size: directional_light_shadow_map.size as u32,
1621 },
1622 ));
1623 view_occlusion_culling_lights.push(view_light_entity);
1624 }
1625
1626 shadow_render_phases
1630 .prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1631 live_shadow_mapping_lights.insert(retained_view_entity);
1632 }
1633 }
1634
1635 commands.entity(entity).insert((
1636 ViewShadowBindings {
1637 point_light_depth_texture: point_light_depth_texture.texture.clone(),
1638 point_light_depth_texture_view: point_light_depth_texture_view.clone(),
1639 directional_light_depth_texture: directional_light_depth_texture.texture.clone(),
1640 directional_light_depth_texture_view: directional_light_depth_texture_view.clone(),
1641 },
1642 ViewLightEntities {
1643 lights: view_lights,
1644 },
1645 ViewLightsUniformOffset {
1646 offset: view_gpu_lights_writer.write(&gpu_lights),
1647 },
1648 ));
1649
1650 if !view_occlusion_culling_lights.is_empty() {
1653 commands
1654 .entity(entity)
1655 .insert(OcclusionCullingSubviewEntities(
1656 view_occlusion_culling_lights,
1657 ));
1658 }
1659 }
1660
1661 for mut entities in &mut light_view_entities {
1663 for (_, light_view_entities) in
1664 entities.extract_if(|entity, _| !live_views.contains(entity))
1665 {
1666 despawn_entities(&mut commands, light_view_entities);
1667 }
1668 }
1669
1670 shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
1671}
1672
1673fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
1674 if entities.is_empty() {
1675 return;
1676 }
1677 commands.queue(move |world: &mut World| {
1678 for entity in entities {
1679 world.despawn(entity);
1680 }
1681 });
1682}
1683
1684pub fn check_light_entities_needing_specialization<M: Material>(
1687 needs_specialization: Query<Entity, (With<MeshMaterial3d<M>>, Changed<NotShadowCaster>)>,
1688 mesh_materials: Query<Entity, With<MeshMaterial3d<M>>>,
1689 mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
1690 mut removed_components: RemovedComponents<NotShadowCaster>,
1691) {
1692 for entity in &needs_specialization {
1693 entities_needing_specialization.push(entity);
1694 }
1695
1696 for removed in removed_components.read() {
1697 if mesh_materials.contains(removed) {
1699 entities_needing_specialization.entities.push(removed);
1700 }
1701 }
1702}
1703
1704#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
1705pub struct LightKeyCache(HashMap<RetainedViewEntity, MeshPipelineKey>);
1706
1707#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
1708pub struct LightSpecializationTicks(HashMap<RetainedViewEntity, Tick>);
1709
1710#[derive(Resource, Deref, DerefMut, Default)]
1711pub struct SpecializedShadowMaterialPipelineCache {
1712 #[deref]
1714 map: HashMap<RetainedViewEntity, SpecializedShadowMaterialViewPipelineCache>,
1715}
1716
1717#[derive(Deref, DerefMut, Default)]
1718pub struct SpecializedShadowMaterialViewPipelineCache {
1719 #[deref]
1720 map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
1721}
1722
1723pub fn check_views_lights_need_specialization(
1724 view_lights: Query<&ViewLightEntities, With<ExtractedView>>,
1725 view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1726 shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
1727 mut light_key_cache: ResMut<LightKeyCache>,
1728 mut light_specialization_ticks: ResMut<LightSpecializationTicks>,
1729 ticks: SystemChangeTick,
1730) {
1731 for view_lights in &view_lights {
1732 for view_light_entity in view_lights.lights.iter().copied() {
1733 let Ok((light_entity, extracted_view_light)) =
1734 view_light_entities.get(view_light_entity)
1735 else {
1736 continue;
1737 };
1738 if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
1739 continue;
1740 }
1741
1742 let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
1743 let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
1744 light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light);
1745 if let Some(current_key) =
1746 light_key_cache.get_mut(&extracted_view_light.retained_view_entity)
1747 {
1748 if *current_key != light_key {
1749 light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
1750 light_specialization_ticks
1751 .insert(extracted_view_light.retained_view_entity, ticks.this_run());
1752 }
1753 } else {
1754 light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
1755 light_specialization_ticks
1756 .insert(extracted_view_light.retained_view_entity, ticks.this_run());
1757 }
1758 }
1759 }
1760}
1761
1762pub fn specialize_shadows(
1763 prepass_pipeline: Res<PrepassPipeline>,
1764 (render_meshes, render_mesh_instances, render_materials, render_material_instances): (
1765 Res<RenderAssets<RenderMesh>>,
1766 Res<RenderMeshInstances>,
1767 Res<ErasedRenderAssets<PreparedMaterial>>,
1768 Res<RenderMaterialInstances>,
1769 ),
1770 shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
1771 mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipelineSpecializer>>,
1772 pipeline_cache: Res<PipelineCache>,
1773 render_lightmaps: Res<RenderLightmaps>,
1774 view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
1775 view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1776 point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
1777 directional_light_entities: Query<
1778 &RenderCascadesVisibleEntities,
1779 With<ExtractedDirectionalLight>,
1780 >,
1781 spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
1782 light_key_cache: Res<LightKeyCache>,
1783 mut specialized_material_pipeline_cache: ResMut<SpecializedShadowMaterialPipelineCache>,
1784 light_specialization_ticks: Res<LightSpecializationTicks>,
1785 entity_specialization_ticks: Res<EntitySpecializationTicks>,
1786 ticks: SystemChangeTick,
1787) {
1788 let mut all_shadow_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
1791
1792 for (entity, view_lights) in &view_lights {
1793 for view_light_entity in view_lights.lights.iter().copied() {
1794 let Ok((light_entity, extracted_view_light)) =
1795 view_light_entities.get(view_light_entity)
1796 else {
1797 continue;
1798 };
1799
1800 all_shadow_views.insert(extracted_view_light.retained_view_entity);
1801
1802 if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
1803 continue;
1804 }
1805 let Some(light_key) = light_key_cache.get(&extracted_view_light.retained_view_entity)
1806 else {
1807 continue;
1808 };
1809
1810 let visible_entities = match light_entity {
1811 LightEntity::Directional {
1812 light_entity,
1813 cascade_index,
1814 } => directional_light_entities
1815 .get(*light_entity)
1816 .expect("Failed to get directional light visible entities")
1817 .entities
1818 .get(&entity)
1819 .expect("Failed to get directional light visible entities for view")
1820 .get(*cascade_index)
1821 .expect("Failed to get directional light visible entities for cascade"),
1822 LightEntity::Point {
1823 light_entity,
1824 face_index,
1825 } => point_light_entities
1826 .get(*light_entity)
1827 .expect("Failed to get point light visible entities")
1828 .get(*face_index),
1829 LightEntity::Spot { light_entity } => spot_light_entities
1830 .get(*light_entity)
1831 .expect("Failed to get spot light visible entities"),
1832 };
1833
1834 let view_tick = light_specialization_ticks
1838 .get(&extracted_view_light.retained_view_entity)
1839 .unwrap();
1840 let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
1841 .entry(extracted_view_light.retained_view_entity)
1842 .or_default();
1843
1844 for (_, visible_entity) in visible_entities.iter().copied() {
1845 let Some(material_instance) =
1846 render_material_instances.instances.get(&visible_entity)
1847 else {
1848 continue;
1849 };
1850
1851 let Some(mesh_instance) =
1852 render_mesh_instances.render_mesh_queue_data(visible_entity)
1853 else {
1854 continue;
1855 };
1856 let entity_tick = entity_specialization_ticks.get(&visible_entity).unwrap();
1857 let last_specialized_tick = view_specialized_material_pipeline_cache
1858 .get(&visible_entity)
1859 .map(|(tick, _)| *tick);
1860 let needs_specialization = last_specialized_tick.is_none_or(|tick| {
1861 view_tick.is_newer_than(tick, ticks.this_run())
1862 || entity_tick
1863 .system_tick
1864 .is_newer_than(tick, ticks.this_run())
1865 });
1866 if !needs_specialization {
1867 continue;
1868 }
1869 let Some(material) = render_materials.get(material_instance.asset_id) else {
1870 continue;
1871 };
1872 if !material.properties.shadows_enabled {
1873 continue;
1875 }
1876 if !mesh_instance
1877 .flags
1878 .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
1879 {
1880 continue;
1881 }
1882 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
1883 continue;
1884 };
1885
1886 let mut mesh_key =
1887 *light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
1888
1889 if render_lightmaps
1895 .render_lightmaps
1896 .contains_key(&visible_entity)
1897 {
1898 mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1899 }
1900
1901 mesh_key |= match material.properties.alpha_mode {
1902 AlphaMode::Mask(_)
1903 | AlphaMode::Blend
1904 | AlphaMode::Premultiplied
1905 | AlphaMode::Add
1906 | AlphaMode::AlphaToCoverage => MeshPipelineKey::MAY_DISCARD,
1907 _ => MeshPipelineKey::NONE,
1908 };
1909 let erased_key = ErasedMaterialPipelineKey {
1910 mesh_key,
1911 material_key: material.properties.material_key.clone(),
1912 type_id: material_instance.asset_id.type_id(),
1913 };
1914 let material_pipeline_specializer = PrepassPipelineSpecializer {
1915 pipeline: prepass_pipeline.clone(),
1916 properties: material.properties.clone(),
1917 };
1918 let pipeline_id = pipelines.specialize(
1919 &pipeline_cache,
1920 &material_pipeline_specializer,
1921 erased_key,
1922 &mesh.layout,
1923 );
1924 let pipeline_id = match pipeline_id {
1925 Ok(id) => id,
1926 Err(err) => {
1927 error!("{}", err);
1928 continue;
1929 }
1930 };
1931
1932 view_specialized_material_pipeline_cache
1933 .insert(visible_entity, (ticks.this_run(), pipeline_id));
1934 }
1935 }
1936 }
1937
1938 specialized_material_pipeline_cache.retain(|view, _| all_shadow_views.contains(view));
1940}
1941
1942pub fn queue_shadows(
1946 render_mesh_instances: Res<RenderMeshInstances>,
1947 render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
1948 render_material_instances: Res<RenderMaterialInstances>,
1949 mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
1950 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1951 mesh_allocator: Res<MeshAllocator>,
1952 view_lights: Query<(Entity, &ViewLightEntities, Option<&RenderLayers>), With<ExtractedView>>,
1953 view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1954 point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
1955 directional_light_entities: Query<
1956 &RenderCascadesVisibleEntities,
1957 With<ExtractedDirectionalLight>,
1958 >,
1959 spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
1960 specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache>,
1961) {
1962 for (entity, view_lights, camera_layers) in &view_lights {
1963 for view_light_entity in view_lights.lights.iter().copied() {
1964 let Ok((light_entity, extracted_view_light)) =
1965 view_light_entities.get(view_light_entity)
1966 else {
1967 continue;
1968 };
1969 let Some(shadow_phase) =
1970 shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity)
1971 else {
1972 continue;
1973 };
1974
1975 let Some(view_specialized_material_pipeline_cache) =
1976 specialized_material_pipeline_cache.get(&extracted_view_light.retained_view_entity)
1977 else {
1978 continue;
1979 };
1980
1981 let visible_entities = match light_entity {
1982 LightEntity::Directional {
1983 light_entity,
1984 cascade_index,
1985 } => directional_light_entities
1986 .get(*light_entity)
1987 .expect("Failed to get directional light visible entities")
1988 .entities
1989 .get(&entity)
1990 .expect("Failed to get directional light visible entities for view")
1991 .get(*cascade_index)
1992 .expect("Failed to get directional light visible entities for cascade"),
1993 LightEntity::Point {
1994 light_entity,
1995 face_index,
1996 } => point_light_entities
1997 .get(*light_entity)
1998 .expect("Failed to get point light visible entities")
1999 .get(*face_index),
2000 LightEntity::Spot { light_entity } => spot_light_entities
2001 .get(*light_entity)
2002 .expect("Failed to get spot light visible entities"),
2003 };
2004
2005 for (entity, main_entity) in visible_entities.iter().copied() {
2006 let Some((current_change_tick, pipeline_id)) =
2007 view_specialized_material_pipeline_cache.get(&main_entity)
2008 else {
2009 continue;
2010 };
2011
2012 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity)
2013 else {
2014 continue;
2015 };
2016 if !mesh_instance
2017 .flags
2018 .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
2019 {
2020 continue;
2021 }
2022
2023 let mesh_layers = mesh_instance
2024 .shared
2025 .render_layers
2026 .as_ref()
2027 .unwrap_or_default();
2028
2029 let camera_layers = camera_layers.unwrap_or_default();
2030
2031 if !camera_layers.intersects(mesh_layers) {
2032 continue;
2033 }
2034
2035 if shadow_phase.validate_cached_entity(main_entity, *current_change_tick) {
2037 continue;
2038 }
2039
2040 let Some(material_instance) = render_material_instances.instances.get(&main_entity)
2041 else {
2042 continue;
2043 };
2044 let Some(material) = render_materials.get(material_instance.asset_id) else {
2045 continue;
2046 };
2047 let Some(draw_function) =
2048 material.properties.get_draw_function(ShadowsDrawFunction)
2049 else {
2050 continue;
2051 };
2052
2053 let (vertex_slab, index_slab) =
2054 mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
2055
2056 let batch_set_key = ShadowBatchSetKey {
2057 pipeline: *pipeline_id,
2058 draw_function,
2059 material_bind_group_index: Some(material.binding.group.0),
2060 vertex_slab: vertex_slab.unwrap_or_default(),
2061 index_slab,
2062 };
2063
2064 shadow_phase.add(
2065 batch_set_key,
2066 ShadowBinKey {
2067 asset_id: mesh_instance.mesh_asset_id.into(),
2068 },
2069 (entity, main_entity),
2070 mesh_instance.current_uniform_index,
2071 BinnedRenderPhaseType::mesh(
2072 mesh_instance.should_batch(),
2073 &gpu_preprocessing_support,
2074 ),
2075 *current_change_tick,
2076 );
2077 }
2078 }
2079 }
2080}
2081
2082pub struct Shadow {
2083 pub batch_set_key: ShadowBatchSetKey,
2088 pub bin_key: ShadowBinKey,
2090 pub representative_entity: (Entity, MainEntity),
2091 pub batch_range: Range<u32>,
2092 pub extra_index: PhaseItemExtraIndex,
2093}
2094
2095#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2101pub struct ShadowBatchSetKey {
2102 pub pipeline: CachedRenderPipelineId,
2104
2105 pub draw_function: DrawFunctionId,
2107
2108 pub material_bind_group_index: Option<u32>,
2112
2113 pub vertex_slab: SlabId,
2118
2119 pub index_slab: Option<SlabId>,
2123}
2124
2125impl PhaseItemBatchSetKey for ShadowBatchSetKey {
2126 fn indexed(&self) -> bool {
2127 self.index_slab.is_some()
2128 }
2129}
2130
2131#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2133pub struct ShadowBinKey {
2134 pub asset_id: UntypedAssetId,
2136}
2137
2138impl PhaseItem for Shadow {
2139 #[inline]
2140 fn entity(&self) -> Entity {
2141 self.representative_entity.0
2142 }
2143
2144 fn main_entity(&self) -> MainEntity {
2145 self.representative_entity.1
2146 }
2147
2148 #[inline]
2149 fn draw_function(&self) -> DrawFunctionId {
2150 self.batch_set_key.draw_function
2151 }
2152
2153 #[inline]
2154 fn batch_range(&self) -> &Range<u32> {
2155 &self.batch_range
2156 }
2157
2158 #[inline]
2159 fn batch_range_mut(&mut self) -> &mut Range<u32> {
2160 &mut self.batch_range
2161 }
2162
2163 #[inline]
2164 fn extra_index(&self) -> PhaseItemExtraIndex {
2165 self.extra_index.clone()
2166 }
2167
2168 #[inline]
2169 fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
2170 (&mut self.batch_range, &mut self.extra_index)
2171 }
2172}
2173
2174impl BinnedPhaseItem for Shadow {
2175 type BatchSetKey = ShadowBatchSetKey;
2176 type BinKey = ShadowBinKey;
2177
2178 #[inline]
2179 fn new(
2180 batch_set_key: Self::BatchSetKey,
2181 bin_key: Self::BinKey,
2182 representative_entity: (Entity, MainEntity),
2183 batch_range: Range<u32>,
2184 extra_index: PhaseItemExtraIndex,
2185 ) -> Self {
2186 Shadow {
2187 batch_set_key,
2188 bin_key,
2189 representative_entity,
2190 batch_range,
2191 extra_index,
2192 }
2193 }
2194}
2195
2196impl CachedRenderPipelinePhaseItem for Shadow {
2197 #[inline]
2198 fn cached_pipeline(&self) -> CachedRenderPipelineId {
2199 self.batch_set_key.pipeline
2200 }
2201}
2202
2203#[derive(Deref, DerefMut)]
2209pub struct EarlyShadowPassNode(ShadowPassNode);
2210
2211#[derive(Deref, DerefMut)]
2216pub struct LateShadowPassNode(ShadowPassNode);
2217
2218pub struct ShadowPassNode {
2221 main_view_query: QueryState<Read<ViewLightEntities>>,
2223 view_light_query: QueryState<(Read<ShadowView>, Read<ExtractedView>, Has<OcclusionCulling>)>,
2225}
2226
2227impl FromWorld for EarlyShadowPassNode {
2228 fn from_world(world: &mut World) -> Self {
2229 Self(ShadowPassNode::from_world(world))
2230 }
2231}
2232
2233impl FromWorld for LateShadowPassNode {
2234 fn from_world(world: &mut World) -> Self {
2235 Self(ShadowPassNode::from_world(world))
2236 }
2237}
2238
2239impl FromWorld for ShadowPassNode {
2240 fn from_world(world: &mut World) -> Self {
2241 Self {
2242 main_view_query: QueryState::new(world),
2243 view_light_query: QueryState::new(world),
2244 }
2245 }
2246}
2247
2248impl Node for EarlyShadowPassNode {
2249 fn update(&mut self, world: &mut World) {
2250 self.0.update(world);
2251 }
2252
2253 fn run<'w>(
2254 &self,
2255 graph: &mut RenderGraphContext,
2256 render_context: &mut RenderContext<'w>,
2257 world: &'w World,
2258 ) -> Result<(), NodeRunError> {
2259 self.0.run(graph, render_context, world, false)
2260 }
2261}
2262
2263impl Node for LateShadowPassNode {
2264 fn update(&mut self, world: &mut World) {
2265 self.0.update(world);
2266 }
2267
2268 fn run<'w>(
2269 &self,
2270 graph: &mut RenderGraphContext,
2271 render_context: &mut RenderContext<'w>,
2272 world: &'w World,
2273 ) -> Result<(), NodeRunError> {
2274 self.0.run(graph, render_context, world, true)
2275 }
2276}
2277
2278impl ShadowPassNode {
2279 fn update(&mut self, world: &mut World) {
2280 self.main_view_query.update_archetypes(world);
2281 self.view_light_query.update_archetypes(world);
2282 }
2283
2284 fn run<'w>(
2289 &self,
2290 graph: &mut RenderGraphContext,
2291 render_context: &mut RenderContext<'w>,
2292 world: &'w World,
2293 is_late: bool,
2294 ) -> Result<(), NodeRunError> {
2295 let Some(shadow_render_phases) = world.get_resource::<ViewBinnedRenderPhases<Shadow>>()
2296 else {
2297 return Ok(());
2298 };
2299
2300 if let Ok(view_lights) = self.main_view_query.get_manual(world, graph.view_entity()) {
2301 for view_light_entity in view_lights.lights.iter().copied() {
2302 let Ok((view_light, extracted_light_view, occlusion_culling)) =
2303 self.view_light_query.get_manual(world, view_light_entity)
2304 else {
2305 continue;
2306 };
2307
2308 if is_late && !occlusion_culling {
2311 continue;
2312 }
2313
2314 let Some(shadow_phase) =
2315 shadow_render_phases.get(&extracted_light_view.retained_view_entity)
2316 else {
2317 continue;
2318 };
2319
2320 let depth_stencil_attachment =
2321 Some(view_light.depth_attachment.get_attachment(StoreOp::Store));
2322
2323 let diagnostics = render_context.diagnostic_recorder();
2324 render_context.add_command_buffer_generation_task(move |render_device| {
2325 #[cfg(feature = "trace")]
2326 let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered();
2327 let mut command_encoder =
2328 render_device.create_command_encoder(&CommandEncoderDescriptor {
2329 label: Some("shadow_pass_command_encoder"),
2330 });
2331
2332 let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
2333 label: Some(&view_light.pass_name),
2334 color_attachments: &[],
2335 depth_stencil_attachment,
2336 timestamp_writes: None,
2337 occlusion_query_set: None,
2338 });
2339
2340 let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
2341 let pass_span =
2342 diagnostics.pass_span(&mut render_pass, view_light.pass_name.clone());
2343
2344 if let Err(err) =
2345 shadow_phase.render(&mut render_pass, world, view_light_entity)
2346 {
2347 error!("Error encountered while rendering the shadow phase {err:?}");
2348 }
2349
2350 pass_span.end(&mut render_pass);
2351 drop(render_pass);
2352 command_encoder.finish()
2353 });
2354 }
2355 }
2356
2357 Ok(())
2358 }
2359}
2360
2361fn point_or_spot_light_to_clusterable(point_light: &ExtractedPointLight) -> ClusterableObjectType {
2363 match point_light.spot_light_angles {
2364 Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
2365 outer_angle,
2366 shadows_enabled: point_light.shadows_enabled,
2367 volumetric: point_light.volumetric,
2368 },
2369 None => ClusterableObjectType::PointLight {
2370 shadows_enabled: point_light.shadows_enabled,
2371 volumetric: point_light.volumetric,
2372 },
2373 }
2374}