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