1use crate::*;
2use bevy_asset::UntypedAssetId;
3use bevy_color::ColorToComponents;
4use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
5use bevy_derive::{Deref, DerefMut};
6use bevy_ecs::{
7 entity::{EntityHash, EntityHashMap, EntityHashSet},
8 prelude::*,
9 system::lifetimeless::Read,
10};
11use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
12use bevy_render::camera::SortedCameras;
13use bevy_render::sync_world::{MainEntity, RenderEntity, TemporaryRenderEntity};
14use bevy_render::{
15 diagnostic::RecordDiagnostics,
16 mesh::RenderMesh,
17 primitives::{CascadesFrusta, CubemapFrusta, Frustum, HalfSpace},
18 render_asset::RenderAssets,
19 render_graph::{Node, NodeRunError, RenderGraphContext},
20 render_phase::*,
21 render_resource::*,
22 renderer::{RenderContext, RenderDevice, RenderQueue},
23 texture::*,
24 view::{ExtractedView, RenderLayers, ViewVisibility},
25 Extract,
26};
27use bevy_transform::{components::GlobalTransform, prelude::Transform};
28#[cfg(feature = "trace")]
29use bevy_utils::tracing::info_span;
30use bevy_utils::{
31 default,
32 tracing::{error, warn},
33 HashMap,
34};
35use core::{hash::Hash, ops::Range};
36
37#[derive(Component)]
38pub struct ExtractedPointLight {
39 pub color: LinearRgba,
40 pub intensity: f32,
42 pub range: f32,
43 pub radius: f32,
44 pub transform: GlobalTransform,
45 pub shadows_enabled: bool,
46 pub shadow_depth_bias: f32,
47 pub shadow_normal_bias: f32,
48 pub shadow_map_near_z: f32,
49 pub spot_light_angles: Option<(f32, f32)>,
50 pub volumetric: bool,
51 pub soft_shadows_enabled: bool,
52}
53
54#[derive(Component, Debug)]
55pub struct ExtractedDirectionalLight {
56 pub color: LinearRgba,
57 pub illuminance: f32,
58 pub transform: GlobalTransform,
59 pub shadows_enabled: bool,
60 pub volumetric: bool,
61 pub shadow_depth_bias: f32,
62 pub shadow_normal_bias: f32,
63 pub cascade_shadow_config: CascadeShadowConfig,
64 pub cascades: EntityHashMap<Vec<Cascade>>,
65 pub frusta: EntityHashMap<Vec<Frustum>>,
66 pub render_layers: RenderLayers,
67 pub soft_shadow_size: Option<f32>,
68}
69
70bitflags::bitflags! {
72 #[repr(transparent)]
73 struct PointLightFlags: u32 {
74 const SHADOWS_ENABLED = 1 << 0;
75 const SPOT_LIGHT_Y_NEGATIVE = 1 << 1;
76 const VOLUMETRIC = 1 << 2;
77 const NONE = 0;
78 const UNINITIALIZED = 0xFFFF;
79 }
80}
81
82#[derive(Copy, Clone, ShaderType, Default, Debug)]
83pub struct GpuDirectionalCascade {
84 clip_from_world: Mat4,
85 texel_size: f32,
86 far_bound: f32,
87}
88
89#[derive(Copy, Clone, ShaderType, Default, Debug)]
90pub struct GpuDirectionalLight {
91 cascades: [GpuDirectionalCascade; MAX_CASCADES_PER_LIGHT],
92 color: Vec4,
93 dir_to_light: Vec3,
94 flags: u32,
95 soft_shadow_size: f32,
96 shadow_depth_bias: f32,
97 shadow_normal_bias: f32,
98 num_cascades: u32,
99 cascades_overlap_proportion: f32,
100 depth_texture_base_index: u32,
101 skip: u32,
102}
103
104bitflags::bitflags! {
106 #[repr(transparent)]
107 struct DirectionalLightFlags: u32 {
108 const SHADOWS_ENABLED = 1 << 0;
109 const VOLUMETRIC = 1 << 1;
110 const NONE = 0;
111 const UNINITIALIZED = 0xFFFF;
112 }
113}
114
115#[derive(Copy, Clone, Debug, ShaderType)]
116pub struct GpuLights {
117 directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
118 ambient_color: Vec4,
119 cluster_dimensions: UVec4,
121 cluster_factors: Vec4,
125 n_directional_lights: u32,
126 spot_light_shadowmap_offset: i32,
128}
129
130#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
133pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
134#[cfg(any(
135 not(feature = "webgl"),
136 not(target_arch = "wasm32"),
137 feature = "webgpu"
138))]
139pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
140#[cfg(any(
141 not(feature = "webgl"),
142 not(target_arch = "wasm32"),
143 feature = "webgpu"
144))]
145pub const MAX_CASCADES_PER_LIGHT: usize = 4;
146#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
147pub const MAX_CASCADES_PER_LIGHT: usize = 1;
148
149#[derive(Resource, Clone)]
150pub struct ShadowSamplers {
151 pub point_light_comparison_sampler: Sampler,
152 #[cfg(feature = "experimental_pbr_pcss")]
153 pub point_light_linear_sampler: Sampler,
154 pub directional_light_comparison_sampler: Sampler,
155 #[cfg(feature = "experimental_pbr_pcss")]
156 pub directional_light_linear_sampler: Sampler,
157}
158
159impl FromWorld for ShadowSamplers {
161 fn from_world(world: &mut World) -> Self {
162 let render_device = world.resource::<RenderDevice>();
163
164 let base_sampler_descriptor = SamplerDescriptor {
165 address_mode_u: AddressMode::ClampToEdge,
166 address_mode_v: AddressMode::ClampToEdge,
167 address_mode_w: AddressMode::ClampToEdge,
168 mag_filter: FilterMode::Linear,
169 min_filter: FilterMode::Linear,
170 mipmap_filter: FilterMode::Nearest,
171 ..default()
172 };
173
174 ShadowSamplers {
175 point_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
176 compare: Some(CompareFunction::GreaterEqual),
177 ..base_sampler_descriptor
178 }),
179 #[cfg(feature = "experimental_pbr_pcss")]
180 point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
181 directional_light_comparison_sampler: render_device.create_sampler(
182 &SamplerDescriptor {
183 compare: Some(CompareFunction::GreaterEqual),
184 ..base_sampler_descriptor
185 },
186 ),
187 #[cfg(feature = "experimental_pbr_pcss")]
188 directional_light_linear_sampler: render_device
189 .create_sampler(&base_sampler_descriptor),
190 }
191 }
192}
193
194#[allow(clippy::too_many_arguments)]
195pub fn extract_lights(
196 mut commands: Commands,
197 point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
198 directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
199 global_point_lights: Extract<Res<GlobalVisibleClusterableObjects>>,
200 point_lights: Extract<
201 Query<(
202 RenderEntity,
203 &PointLight,
204 &CubemapVisibleEntities,
205 &GlobalTransform,
206 &ViewVisibility,
207 &CubemapFrusta,
208 Option<&VolumetricLight>,
209 )>,
210 >,
211 spot_lights: Extract<
212 Query<(
213 RenderEntity,
214 &SpotLight,
215 &VisibleMeshEntities,
216 &GlobalTransform,
217 &ViewVisibility,
218 &Frustum,
219 Option<&VolumetricLight>,
220 )>,
221 >,
222 directional_lights: Extract<
223 Query<
224 (
225 RenderEntity,
226 &DirectionalLight,
227 &CascadesVisibleEntities,
228 &Cascades,
229 &CascadeShadowConfig,
230 &CascadesFrusta,
231 &GlobalTransform,
232 &ViewVisibility,
233 Option<&RenderLayers>,
234 Option<&VolumetricLight>,
235 ),
236 Without<SpotLight>,
237 >,
238 >,
239 mapper: Extract<Query<RenderEntity>>,
240 mut previous_point_lights_len: Local<usize>,
241 mut previous_spot_lights_len: Local<usize>,
242) {
243 if point_light_shadow_map.is_changed() {
246 commands.insert_resource(point_light_shadow_map.clone());
247 }
248 if directional_light_shadow_map.is_changed() {
249 commands.insert_resource(directional_light_shadow_map.clone());
250 }
251 let point_light_texel_size = 2.0 / point_light_shadow_map.size as f32;
259
260 let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
261 for entity in global_point_lights.iter().copied() {
262 let Ok((
263 render_entity,
264 point_light,
265 cubemap_visible_entities,
266 transform,
267 view_visibility,
268 frusta,
269 volumetric_light,
270 )) = point_lights.get(entity)
271 else {
272 continue;
273 };
274 if !view_visibility.get() {
275 continue;
276 }
277 let render_cubemap_visible_entities = RenderCubemapVisibleEntities {
278 data: cubemap_visible_entities
279 .iter()
280 .map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v))
281 .collect::<Vec<_>>()
282 .try_into()
283 .unwrap(),
284 };
285
286 let extracted_point_light = ExtractedPointLight {
287 color: point_light.color.into(),
288 intensity: point_light.intensity / (4.0 * core::f32::consts::PI),
292 range: point_light.range,
293 radius: point_light.radius,
294 transform: *transform,
295 shadows_enabled: point_light.shadows_enabled,
296 shadow_depth_bias: point_light.shadow_depth_bias,
297 shadow_normal_bias: point_light.shadow_normal_bias
299 * point_light_texel_size
300 * core::f32::consts::SQRT_2,
301 shadow_map_near_z: point_light.shadow_map_near_z,
302 spot_light_angles: None,
303 volumetric: volumetric_light.is_some(),
304 #[cfg(feature = "experimental_pbr_pcss")]
305 soft_shadows_enabled: point_light.soft_shadows_enabled,
306 #[cfg(not(feature = "experimental_pbr_pcss"))]
307 soft_shadows_enabled: false,
308 };
309 point_lights_values.push((
310 render_entity,
311 (
312 extracted_point_light,
313 render_cubemap_visible_entities,
314 (*frusta).clone(),
315 ),
316 ));
317 }
318 *previous_point_lights_len = point_lights_values.len();
319 commands.insert_or_spawn_batch(point_lights_values);
320
321 let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
322 for entity in global_point_lights.iter().copied() {
323 if let Ok((
324 render_entity,
325 spot_light,
326 visible_entities,
327 transform,
328 view_visibility,
329 frustum,
330 volumetric_light,
331 )) = spot_lights.get(entity)
332 {
333 if !view_visibility.get() {
334 continue;
335 }
336 let render_visible_entities =
337 create_render_visible_mesh_entities(&mut commands, &mapper, visible_entities);
338
339 let texel_size =
340 2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32;
341
342 spot_lights_values.push((
343 render_entity,
344 (
345 ExtractedPointLight {
346 color: spot_light.color.into(),
347 intensity: spot_light.intensity / (4.0 * core::f32::consts::PI),
354 range: spot_light.range,
355 radius: spot_light.radius,
356 transform: *transform,
357 shadows_enabled: spot_light.shadows_enabled,
358 shadow_depth_bias: spot_light.shadow_depth_bias,
359 shadow_normal_bias: spot_light.shadow_normal_bias
361 * texel_size
362 * core::f32::consts::SQRT_2,
363 shadow_map_near_z: spot_light.shadow_map_near_z,
364 spot_light_angles: Some((spot_light.inner_angle, spot_light.outer_angle)),
365 volumetric: volumetric_light.is_some(),
366 #[cfg(feature = "experimental_pbr_pcss")]
367 soft_shadows_enabled: spot_light.soft_shadows_enabled,
368 #[cfg(not(feature = "experimental_pbr_pcss"))]
369 soft_shadows_enabled: false,
370 },
371 render_visible_entities,
372 *frustum,
373 ),
374 ));
375 }
376 }
377 *previous_spot_lights_len = spot_lights_values.len();
378 commands.insert_or_spawn_batch(spot_lights_values);
379
380 for (
381 entity,
382 directional_light,
383 visible_entities,
384 cascades,
385 cascade_config,
386 frusta,
387 transform,
388 view_visibility,
389 maybe_layers,
390 volumetric_light,
391 ) in &directional_lights
392 {
393 if !view_visibility.get() {
394 commands
395 .get_entity(entity)
396 .expect("Light entity wasn't synced.")
397 .remove::<(ExtractedDirectionalLight, RenderCascadesVisibleEntities)>();
398 continue;
399 }
400
401 let mut extracted_cascades = EntityHashMap::default();
403 let mut extracted_frusta = EntityHashMap::default();
404 let mut cascade_visible_entities = EntityHashMap::default();
405 for (e, v) in cascades.cascades.iter() {
406 if let Ok(entity) = mapper.get(*e) {
407 extracted_cascades.insert(entity, v.clone());
408 } else {
409 break;
410 }
411 }
412 for (e, v) in frusta.frusta.iter() {
413 if let Ok(entity) = mapper.get(*e) {
414 extracted_frusta.insert(entity, v.clone());
415 } else {
416 break;
417 }
418 }
419 for (e, v) in visible_entities.entities.iter() {
420 if let Ok(entity) = mapper.get(*e) {
421 cascade_visible_entities.insert(
422 entity,
423 v.iter()
424 .map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v))
425 .collect(),
426 );
427 } else {
428 break;
429 }
430 }
431
432 commands
433 .get_entity(entity)
434 .expect("Light entity wasn't synced.")
435 .insert((
436 ExtractedDirectionalLight {
437 color: directional_light.color.into(),
438 illuminance: directional_light.illuminance,
439 transform: *transform,
440 volumetric: volumetric_light.is_some(),
441 #[cfg(feature = "experimental_pbr_pcss")]
442 soft_shadow_size: directional_light.soft_shadow_size,
443 #[cfg(not(feature = "experimental_pbr_pcss"))]
444 soft_shadow_size: None,
445 shadows_enabled: directional_light.shadows_enabled,
446 shadow_depth_bias: directional_light.shadow_depth_bias,
447 shadow_normal_bias: directional_light.shadow_normal_bias
449 * core::f32::consts::SQRT_2,
450 cascade_shadow_config: cascade_config.clone(),
451 cascades: extracted_cascades,
452 frusta: extracted_frusta,
453 render_layers: maybe_layers.unwrap_or_default().clone(),
454 },
455 RenderCascadesVisibleEntities {
456 entities: cascade_visible_entities,
457 },
458 ));
459 }
460}
461
462fn create_render_visible_mesh_entities(
463 commands: &mut Commands,
464 mapper: &Extract<Query<RenderEntity>>,
465 visible_entities: &VisibleMeshEntities,
466) -> RenderVisibleMeshEntities {
467 RenderVisibleMeshEntities {
468 entities: visible_entities
469 .iter()
470 .map(|e| {
471 let render_entity = mapper
472 .get(*e)
473 .unwrap_or_else(|_| commands.spawn(TemporaryRenderEntity).id());
474 (render_entity, MainEntity::from(*e))
475 })
476 .collect(),
477 }
478}
479
480#[derive(Component, Default, Deref, DerefMut)]
481pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
484
485pub(crate) fn add_light_view_entities(
487 trigger: Trigger<OnAdd, (ExtractedDirectionalLight, ExtractedPointLight)>,
488 mut commands: Commands,
489) {
490 if let Some(mut v) = commands.get_entity(trigger.entity()) {
491 v.insert(LightViewEntities::default());
492 }
493}
494
495pub(crate) fn extracted_light_removed(
497 trigger: Trigger<OnRemove, (ExtractedDirectionalLight, ExtractedPointLight)>,
498 mut commands: Commands,
499) {
500 if let Some(mut v) = commands.get_entity(trigger.entity()) {
501 v.remove::<LightViewEntities>();
502 }
503}
504
505pub(crate) fn remove_light_view_entities(
506 trigger: Trigger<OnRemove, LightViewEntities>,
507 query: Query<&LightViewEntities>,
508 mut commands: Commands,
509) {
510 if let Ok(entities) = query.get(trigger.entity()) {
511 for v in entities.0.values() {
512 for e in v.iter().copied() {
513 if let Some(mut v) = commands.get_entity(e) {
514 v.despawn();
515 }
516 }
517 }
518 }
519}
520
521pub(crate) struct CubeMapFace {
522 pub(crate) target: Vec3,
523 pub(crate) up: Vec3,
524}
525
526pub(crate) const CUBE_MAP_FACES: [CubeMapFace; 6] = [
534 CubeMapFace {
536 target: Vec3::X,
537 up: Vec3::Y,
538 },
539 CubeMapFace {
541 target: Vec3::NEG_X,
542 up: Vec3::Y,
543 },
544 CubeMapFace {
546 target: Vec3::Y,
547 up: Vec3::Z,
548 },
549 CubeMapFace {
551 target: Vec3::NEG_Y,
552 up: Vec3::NEG_Z,
553 },
554 CubeMapFace {
556 target: Vec3::NEG_Z,
557 up: Vec3::Y,
558 },
559 CubeMapFace {
561 target: Vec3::Z,
562 up: Vec3::Y,
563 },
564];
565
566fn face_index_to_name(face_index: usize) -> &'static str {
567 match face_index {
568 0 => "+x",
569 1 => "-x",
570 2 => "+y",
571 3 => "-y",
572 4 => "+z",
573 5 => "-z",
574 _ => "invalid",
575 }
576}
577
578#[derive(Component)]
579pub struct ShadowView {
580 pub depth_attachment: DepthAttachment,
581 pub pass_name: String,
582}
583
584#[derive(Component)]
585pub struct ViewShadowBindings {
586 pub point_light_depth_texture: Texture,
587 pub point_light_depth_texture_view: TextureView,
588 pub directional_light_depth_texture: Texture,
589 pub directional_light_depth_texture_view: TextureView,
590}
591
592#[derive(Component)]
593pub struct ViewLightEntities {
594 pub lights: Vec<Entity>,
595}
596
597#[derive(Component)]
598pub struct ViewLightsUniformOffset {
599 pub offset: u32,
600}
601
602#[derive(Resource, Default)]
603pub struct LightMeta {
604 pub view_gpu_lights: DynamicUniformBuffer<GpuLights>,
605}
606
607#[derive(Component)]
608pub enum LightEntity {
609 Directional {
610 light_entity: Entity,
611 cascade_index: usize,
612 },
613 Point {
614 light_entity: Entity,
615 face_index: usize,
616 },
617 Spot {
618 light_entity: Entity,
619 },
620}
621pub fn calculate_cluster_factors(
622 near: f32,
623 far: f32,
624 z_slices: f32,
625 is_orthographic: bool,
626) -> Vec2 {
627 if is_orthographic {
628 Vec2::new(-near, z_slices / (-far - -near))
629 } else {
630 let z_slices_of_ln_zfar_over_znear = (z_slices - 1.0) / ops::ln(far / near);
631 Vec2::new(
632 z_slices_of_ln_zfar_over_znear,
633 ops::ln(near) * z_slices_of_ln_zfar_over_znear,
634 )
635 }
636}
637
638pub(crate) fn spot_light_world_from_view(transform: &GlobalTransform) -> Mat4 {
643 let fwd_dir = transform.back().extend(0.0);
645
646 let sign = 1f32.copysign(fwd_dir.z);
647 let a = -1.0 / (fwd_dir.z + sign);
648 let b = fwd_dir.x * fwd_dir.y * a;
649 let up_dir = Vec4::new(
650 1.0 + sign * fwd_dir.x * fwd_dir.x * a,
651 sign * b,
652 -sign * fwd_dir.x,
653 0.0,
654 );
655 let right_dir = Vec4::new(-b, -sign - fwd_dir.y * fwd_dir.y * a, fwd_dir.y, 0.0);
656
657 Mat4::from_cols(
658 right_dir,
659 up_dir,
660 fwd_dir,
661 transform.translation().extend(1.0),
662 )
663}
664
665pub(crate) fn spot_light_clip_from_view(angle: f32, near_z: f32) -> Mat4 {
666 Mat4::perspective_infinite_reverse_rh(angle * 2.0, 1.0, near_z)
668}
669
670#[allow(clippy::too_many_arguments)]
671pub fn prepare_lights(
672 mut commands: Commands,
673 mut texture_cache: ResMut<TextureCache>,
674 render_device: Res<RenderDevice>,
675 render_queue: Res<RenderQueue>,
676 mut global_light_meta: ResMut<GlobalClusterableObjectMeta>,
677 mut light_meta: ResMut<LightMeta>,
678 views: Query<
679 (
680 Entity,
681 &ExtractedView,
682 &ExtractedClusterConfig,
683 Option<&RenderLayers>,
684 ),
685 With<Camera3d>,
686 >,
687 ambient_light: Res<AmbientLight>,
688 point_light_shadow_map: Res<PointLightShadowMap>,
689 directional_light_shadow_map: Res<DirectionalLightShadowMap>,
690 mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
691 (
692 mut max_directional_lights_warning_emitted,
693 mut max_cascades_per_light_warning_emitted,
694 mut live_shadow_mapping_lights,
695 ): (Local<bool>, Local<bool>, Local<EntityHashSet>),
696 point_lights: Query<(
697 Entity,
698 &ExtractedPointLight,
699 AnyOf<(&CubemapFrusta, &Frustum)>,
700 )>,
701 directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
702 mut light_view_entities: Query<&mut LightViewEntities>,
703 sorted_cameras: Res<SortedCameras>,
704) {
705 let views_iter = views.iter();
706 let views_count = views_iter.len();
707 let Some(mut view_gpu_lights_writer) =
708 light_meta
709 .view_gpu_lights
710 .get_writer(views_count, &render_device, &render_queue)
711 else {
712 return;
713 };
714
715 let cube_face_rotations = CUBE_MAP_FACES
717 .iter()
718 .map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
719 .collect::<Vec<_>>();
720
721 global_light_meta.entity_to_index.clear();
722
723 let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
724 let mut directional_lights: Vec<_> = directional_lights.iter().collect::<Vec<_>>();
725
726 #[cfg(any(
727 not(feature = "webgl"),
728 not(target_arch = "wasm32"),
729 feature = "webgpu"
730 ))]
731 let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize;
732 #[cfg(any(
733 not(feature = "webgl"),
734 not(target_arch = "wasm32"),
735 feature = "webgpu"
736 ))]
737 let max_texture_cubes = max_texture_array_layers / 6;
738 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
739 let max_texture_array_layers = 1;
740 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
741 let max_texture_cubes = 1;
742
743 if !*max_directional_lights_warning_emitted && directional_lights.len() > MAX_DIRECTIONAL_LIGHTS
744 {
745 warn!(
746 "The amount of directional lights of {} is exceeding the supported limit of {}.",
747 directional_lights.len(),
748 MAX_DIRECTIONAL_LIGHTS
749 );
750 *max_directional_lights_warning_emitted = true;
751 }
752
753 if !*max_cascades_per_light_warning_emitted
754 && directional_lights
755 .iter()
756 .any(|(_, light)| light.cascade_shadow_config.bounds.len() > MAX_CASCADES_PER_LIGHT)
757 {
758 warn!(
759 "The number of cascades configured for a directional light exceeds the supported limit of {}.",
760 MAX_CASCADES_PER_LIGHT
761 );
762 *max_cascades_per_light_warning_emitted = true;
763 }
764
765 let point_light_count = point_lights
766 .iter()
767 .filter(|light| light.1.spot_light_angles.is_none())
768 .count();
769
770 let point_light_volumetric_enabled_count = point_lights
771 .iter()
772 .filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_none())
773 .count()
774 .min(max_texture_cubes);
775
776 let point_light_shadow_maps_count = point_lights
777 .iter()
778 .filter(|light| light.1.shadows_enabled && light.1.spot_light_angles.is_none())
779 .count()
780 .min(max_texture_cubes);
781
782 let directional_volumetric_enabled_count = directional_lights
783 .iter()
784 .take(MAX_DIRECTIONAL_LIGHTS)
785 .filter(|(_, light)| light.volumetric)
786 .count()
787 .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
788
789 let directional_shadow_enabled_count = directional_lights
790 .iter()
791 .take(MAX_DIRECTIONAL_LIGHTS)
792 .filter(|(_, light)| light.shadows_enabled)
793 .count()
794 .min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
795
796 let spot_light_count = point_lights
797 .iter()
798 .filter(|(_, light, _)| light.spot_light_angles.is_some())
799 .count()
800 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
801
802 let spot_light_volumetric_enabled_count = point_lights
803 .iter()
804 .filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_some())
805 .count()
806 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
807
808 let spot_light_shadow_maps_count = point_lights
809 .iter()
810 .filter(|(_, light, _)| light.shadows_enabled && light.spot_light_angles.is_some())
811 .count()
812 .min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);
813
814 point_lights.sort_by(|(entity_1, light_1, _), (entity_2, light_2, _)| {
820 clusterable_object_order(
821 ClusterableObjectOrderData {
822 entity: entity_1,
823 shadows_enabled: &light_1.shadows_enabled,
824 is_volumetric_light: &light_1.volumetric,
825 is_spot_light: &light_1.spot_light_angles.is_some(),
826 },
827 ClusterableObjectOrderData {
828 entity: entity_2,
829 shadows_enabled: &light_2.shadows_enabled,
830 is_volumetric_light: &light_2.volumetric,
831 is_spot_light: &light_2.spot_light_angles.is_some(),
832 },
833 )
834 });
835
836 directional_lights.sort_by(|(entity_1, light_1), (entity_2, light_2)| {
845 directional_light_order(
846 (entity_1, &light_1.volumetric, &light_1.shadows_enabled),
847 (entity_2, &light_2.volumetric, &light_2.shadows_enabled),
848 )
849 });
850
851 if global_light_meta.entity_to_index.capacity() < point_lights.len() {
852 global_light_meta
853 .entity_to_index
854 .reserve(point_lights.len());
855 }
856
857 let mut gpu_point_lights = Vec::new();
858 for (index, &(entity, light, _)) in point_lights.iter().enumerate() {
859 let mut flags = PointLightFlags::NONE;
860
861 if light.shadows_enabled
863 && (index < point_light_shadow_maps_count
864 || (light.spot_light_angles.is_some()
865 && index - point_light_count < spot_light_shadow_maps_count))
866 {
867 flags |= PointLightFlags::SHADOWS_ENABLED;
868 }
869
870 let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
871 core::f32::consts::FRAC_PI_2,
872 1.0,
873 light.shadow_map_near_z,
874 );
875 if light.shadows_enabled
876 && light.volumetric
877 && (index < point_light_volumetric_enabled_count
878 || (light.spot_light_angles.is_some()
879 && index - point_light_count < spot_light_volumetric_enabled_count))
880 {
881 flags |= PointLightFlags::VOLUMETRIC;
882 }
883
884 let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles {
885 Some((inner, outer)) => {
886 let light_direction = light.transform.forward();
887 if light_direction.y.is_sign_negative() {
888 flags |= PointLightFlags::SPOT_LIGHT_Y_NEGATIVE;
889 }
890
891 let cos_outer = ops::cos(outer);
892 let spot_scale = 1.0 / f32::max(ops::cos(inner) - cos_outer, 1e-4);
893 let spot_offset = -cos_outer * spot_scale;
894
895 (
896 light_direction.xz().extend(spot_scale).extend(spot_offset),
898 ops::tan(outer),
899 )
900 }
901 None => {
902 (
903 Vec4::new(
905 cube_face_projection.z_axis.z,
906 cube_face_projection.z_axis.w,
907 cube_face_projection.w_axis.z,
908 cube_face_projection.w_axis.w,
909 ),
910 0.0,
912 )
913 }
914 };
915
916 gpu_point_lights.push(GpuClusterableObject {
917 light_custom_data,
918 color_inverse_square_range: (Vec4::from_slice(&light.color.to_f32_array())
921 * light.intensity)
922 .xyz()
923 .extend(1.0 / (light.range * light.range)),
924 position_radius: light.transform.translation().extend(light.radius),
925 flags: flags.bits(),
926 shadow_depth_bias: light.shadow_depth_bias,
927 shadow_normal_bias: light.shadow_normal_bias,
928 shadow_map_near_z: light.shadow_map_near_z,
929 spot_light_tan_angle,
930 pad_a: 0.0,
931 pad_b: 0.0,
932 soft_shadow_size: if light.soft_shadows_enabled {
933 light.radius
934 } else {
935 0.0
936 },
937 });
938 global_light_meta.entity_to_index.insert(entity, index);
939 }
940
941 let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
942 let mut num_directional_cascades_enabled = 0usize;
943 for (index, (_light_entity, light)) in directional_lights
944 .iter()
945 .enumerate()
946 .take(MAX_DIRECTIONAL_LIGHTS)
947 {
948 let mut flags = DirectionalLightFlags::NONE;
949
950 if light.volumetric
952 && light.shadows_enabled
953 && (index < directional_volumetric_enabled_count)
954 {
955 flags |= DirectionalLightFlags::VOLUMETRIC;
956 }
957 if light.shadows_enabled && (index < directional_shadow_enabled_count) {
959 flags |= DirectionalLightFlags::SHADOWS_ENABLED;
960 }
961
962 let num_cascades = light
963 .cascade_shadow_config
964 .bounds
965 .len()
966 .min(MAX_CASCADES_PER_LIGHT);
967 gpu_directional_lights[index] = GpuDirectionalLight {
968 skip: 0u32,
970 cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
972 color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
975 dir_to_light: light.transform.back().into(),
977 flags: flags.bits(),
978 soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
979 shadow_depth_bias: light.shadow_depth_bias,
980 shadow_normal_bias: light.shadow_normal_bias,
981 num_cascades: num_cascades as u32,
982 cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
983 depth_texture_base_index: num_directional_cascades_enabled as u32,
984 };
985 if index < directional_shadow_enabled_count {
986 num_directional_cascades_enabled += num_cascades;
987 }
988 }
989
990 global_light_meta
991 .gpu_clusterable_objects
992 .set(gpu_point_lights);
993 global_light_meta
994 .gpu_clusterable_objects
995 .write_buffer(&render_device, &render_queue);
996
997 live_shadow_mapping_lights.clear();
998
999 let mut point_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1000 let mut directional_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
1001
1002 let point_light_depth_texture = texture_cache.get(
1003 &render_device,
1004 TextureDescriptor {
1005 size: Extent3d {
1006 width: point_light_shadow_map.size as u32,
1007 height: point_light_shadow_map.size as u32,
1008 depth_or_array_layers: point_light_shadow_maps_count.max(1) as u32 * 6,
1009 },
1010 mip_level_count: 1,
1011 sample_count: 1,
1012 dimension: TextureDimension::D2,
1013 format: CORE_3D_DEPTH_FORMAT,
1014 label: Some("point_light_shadow_map_texture"),
1015 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1016 view_formats: &[],
1017 },
1018 );
1019
1020 let point_light_depth_texture_view =
1021 point_light_depth_texture
1022 .texture
1023 .create_view(&TextureViewDescriptor {
1024 label: Some("point_light_shadow_map_array_texture_view"),
1025 format: None,
1026 #[cfg(all(
1029 not(feature = "ios_simulator"),
1030 any(
1031 not(feature = "webgl"),
1032 not(target_arch = "wasm32"),
1033 feature = "webgpu"
1034 )
1035 ))]
1036 dimension: Some(TextureViewDimension::CubeArray),
1037 #[cfg(any(
1038 feature = "ios_simulator",
1039 all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
1040 ))]
1041 dimension: Some(TextureViewDimension::Cube),
1042 aspect: TextureAspect::DepthOnly,
1043 base_mip_level: 0,
1044 mip_level_count: None,
1045 base_array_layer: 0,
1046 array_layer_count: None,
1047 });
1048
1049 let directional_light_depth_texture = texture_cache.get(
1050 &render_device,
1051 TextureDescriptor {
1052 size: Extent3d {
1053 width: (directional_light_shadow_map.size as u32)
1054 .min(render_device.limits().max_texture_dimension_2d),
1055 height: (directional_light_shadow_map.size as u32)
1056 .min(render_device.limits().max_texture_dimension_2d),
1057 depth_or_array_layers: (num_directional_cascades_enabled
1058 + spot_light_shadow_maps_count)
1059 .max(1) as u32,
1060 },
1061 mip_level_count: 1,
1062 sample_count: 1,
1063 dimension: TextureDimension::D2,
1064 format: CORE_3D_DEPTH_FORMAT,
1065 label: Some("directional_light_shadow_map_texture"),
1066 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
1067 view_formats: &[],
1068 },
1069 );
1070
1071 let directional_light_depth_texture_view =
1072 directional_light_depth_texture
1073 .texture
1074 .create_view(&TextureViewDescriptor {
1075 label: Some("directional_light_shadow_map_array_texture_view"),
1076 format: None,
1077 #[cfg(any(
1078 not(feature = "webgl"),
1079 not(target_arch = "wasm32"),
1080 feature = "webgpu"
1081 ))]
1082 dimension: Some(TextureViewDimension::D2Array),
1083 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
1084 dimension: Some(TextureViewDimension::D2),
1085 aspect: TextureAspect::DepthOnly,
1086 base_mip_level: 0,
1087 mip_level_count: None,
1088 base_array_layer: 0,
1089 array_layer_count: None,
1090 });
1091
1092 let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash);
1093
1094 for (entity, extracted_view, clusters, maybe_layers) in sorted_cameras
1096 .0
1097 .iter()
1098 .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
1099 {
1100 live_views.insert(entity);
1101 let mut view_lights = Vec::new();
1102
1103 let is_orthographic = extracted_view.clip_from_view.w_axis.w == 1.0;
1104 let cluster_factors_zw = calculate_cluster_factors(
1105 clusters.near,
1106 clusters.far,
1107 clusters.dimensions.z as f32,
1108 is_orthographic,
1109 );
1110
1111 let n_clusters = clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z;
1112 let mut gpu_lights = GpuLights {
1113 directional_lights: gpu_directional_lights,
1114 ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
1115 * ambient_light.brightness,
1116 cluster_factors: Vec4::new(
1117 clusters.dimensions.x as f32 / extracted_view.viewport.z as f32,
1118 clusters.dimensions.y as f32 / extracted_view.viewport.w as f32,
1119 cluster_factors_zw.x,
1120 cluster_factors_zw.y,
1121 ),
1122 cluster_dimensions: clusters.dimensions.extend(n_clusters),
1123 n_directional_lights: directional_lights.iter().len().min(MAX_DIRECTIONAL_LIGHTS)
1124 as u32,
1125 spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
1129 - point_light_count as i32,
1130 };
1131
1132 for &(light_entity, light, (point_light_frusta, _)) in point_lights
1134 .iter()
1135 .take(point_light_count.min(max_texture_cubes))
1137 {
1138 let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1139 continue;
1140 };
1141
1142 if !light.shadows_enabled {
1143 if let Some(entities) = light_view_entities.remove(&entity) {
1144 despawn_entities(&mut commands, entities);
1145 }
1146 continue;
1147 }
1148
1149 let light_index = *global_light_meta
1150 .entity_to_index
1151 .get(&light_entity)
1152 .unwrap();
1153 let view_translation = GlobalTransform::from_translation(light.transform.translation());
1157
1158 let light_view_entities = light_view_entities
1160 .entry(entity)
1161 .or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect());
1162
1163 let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
1164 core::f32::consts::FRAC_PI_2,
1165 1.0,
1166 light.shadow_map_near_z,
1167 );
1168
1169 for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
1170 .iter()
1171 .zip(&point_light_frusta.unwrap().frusta)
1172 .zip(light_view_entities.iter().copied())
1173 .enumerate()
1174 {
1175 let mut first = false;
1176 let base_array_layer = (light_index * 6 + face_index) as u32;
1177
1178 let depth_attachment = point_light_depth_attachments
1179 .entry(base_array_layer)
1180 .or_insert_with(|| {
1181 first = true;
1182
1183 let depth_texture_view =
1184 point_light_depth_texture
1185 .texture
1186 .create_view(&TextureViewDescriptor {
1187 label: Some("point_light_shadow_map_texture_view"),
1188 format: None,
1189 dimension: Some(TextureViewDimension::D2),
1190 aspect: TextureAspect::All,
1191 base_mip_level: 0,
1192 mip_level_count: None,
1193 base_array_layer,
1194 array_layer_count: Some(1u32),
1195 });
1196
1197 DepthAttachment::new(depth_texture_view, Some(0.0))
1198 })
1199 .clone();
1200
1201 commands.entity(view_light_entity).insert((
1202 ShadowView {
1203 depth_attachment,
1204 pass_name: format!(
1205 "shadow pass point light {} {}",
1206 light_index,
1207 face_index_to_name(face_index)
1208 ),
1209 },
1210 ExtractedView {
1211 viewport: UVec4::new(
1212 0,
1213 0,
1214 point_light_shadow_map.size as u32,
1215 point_light_shadow_map.size as u32,
1216 ),
1217 world_from_view: view_translation * *view_rotation,
1218 clip_from_world: None,
1219 clip_from_view: cube_face_projection,
1220 hdr: false,
1221 color_grading: Default::default(),
1222 },
1223 *frustum,
1224 LightEntity::Point {
1225 light_entity,
1226 face_index,
1227 },
1228 ));
1229
1230 view_lights.push(view_light_entity);
1231
1232 if first {
1233 shadow_render_phases.insert_or_clear(view_light_entity);
1235 live_shadow_mapping_lights.insert(view_light_entity);
1236 }
1237 }
1238 }
1239
1240 for (light_index, &(light_entity, light, (_, spot_light_frustum))) in point_lights
1242 .iter()
1243 .skip(point_light_count)
1244 .take(spot_light_count)
1245 .enumerate()
1246 {
1247 let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1248 continue;
1249 };
1250
1251 if !light.shadows_enabled {
1252 if let Some(entities) = light_view_entities.remove(&entity) {
1253 despawn_entities(&mut commands, entities);
1254 }
1255 continue;
1256 }
1257
1258 let spot_world_from_view = spot_light_world_from_view(&light.transform);
1259 let spot_world_from_view = spot_world_from_view.into();
1260
1261 let angle = light.spot_light_angles.expect("lights should be sorted so that \
1262 [point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
1263 let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
1264
1265 let mut first = false;
1266 let base_array_layer = (num_directional_cascades_enabled + light_index) as u32;
1267
1268 let depth_attachment = directional_light_depth_attachments
1269 .entry(base_array_layer)
1270 .or_insert_with(|| {
1271 first = true;
1272
1273 let depth_texture_view = directional_light_depth_texture.texture.create_view(
1274 &TextureViewDescriptor {
1275 label: Some("spot_light_shadow_map_texture_view"),
1276 format: None,
1277 dimension: Some(TextureViewDimension::D2),
1278 aspect: TextureAspect::All,
1279 base_mip_level: 0,
1280 mip_level_count: None,
1281 base_array_layer,
1282 array_layer_count: Some(1u32),
1283 },
1284 );
1285
1286 DepthAttachment::new(depth_texture_view, Some(0.0))
1287 })
1288 .clone();
1289
1290 let light_view_entities = light_view_entities
1291 .entry(entity)
1292 .or_insert_with(|| vec![commands.spawn_empty().id()]);
1293
1294 let view_light_entity = light_view_entities[0];
1295
1296 commands.entity(view_light_entity).insert((
1297 ShadowView {
1298 depth_attachment,
1299 pass_name: format!("shadow pass spot light {light_index}"),
1300 },
1301 ExtractedView {
1302 viewport: UVec4::new(
1303 0,
1304 0,
1305 directional_light_shadow_map.size as u32,
1306 directional_light_shadow_map.size as u32,
1307 ),
1308 world_from_view: spot_world_from_view,
1309 clip_from_view: spot_projection,
1310 clip_from_world: None,
1311 hdr: false,
1312 color_grading: Default::default(),
1313 },
1314 *spot_light_frustum.unwrap(),
1315 LightEntity::Spot { light_entity },
1316 ));
1317
1318 view_lights.push(view_light_entity);
1319
1320 if first {
1321 shadow_render_phases.insert_or_clear(view_light_entity);
1323 live_shadow_mapping_lights.insert(view_light_entity);
1324 }
1325 }
1326
1327 let mut directional_depth_texture_array_index = 0u32;
1329 let view_layers = maybe_layers.unwrap_or_default();
1330 for (light_index, &(light_entity, light)) in directional_lights
1331 .iter()
1332 .enumerate()
1333 .take(MAX_DIRECTIONAL_LIGHTS)
1334 {
1335 let gpu_light = &mut gpu_lights.directional_lights[light_index];
1336
1337 let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1338 continue;
1339 };
1340
1341 if !view_layers.intersects(&light.render_layers) {
1343 gpu_light.skip = 1u32;
1344 if let Some(entities) = light_view_entities.remove(&entity) {
1345 despawn_entities(&mut commands, entities);
1346 }
1347 continue;
1348 }
1349
1350 if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
1352 if let Some(entities) = light_view_entities.remove(&entity) {
1353 despawn_entities(&mut commands, entities);
1354 }
1355 continue;
1356 }
1357
1358 let cascades = light
1359 .cascades
1360 .get(&entity)
1361 .unwrap()
1362 .iter()
1363 .take(MAX_CASCADES_PER_LIGHT);
1364 let frusta = light
1365 .frusta
1366 .get(&entity)
1367 .unwrap()
1368 .iter()
1369 .take(MAX_CASCADES_PER_LIGHT);
1370
1371 let iter = cascades
1372 .zip(frusta)
1373 .zip(&light.cascade_shadow_config.bounds);
1374
1375 let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
1376 (0..iter.len())
1377 .map(|_| commands.spawn_empty().id())
1378 .collect()
1379 });
1380 if light_view_entities.len() != iter.len() {
1381 let entities = core::mem::take(light_view_entities);
1382 despawn_entities(&mut commands, entities);
1383 light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
1384 }
1385
1386 for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
1387 iter.zip(light_view_entities.iter().copied()).enumerate()
1388 {
1389 gpu_lights.directional_lights[light_index].cascades[cascade_index] =
1390 GpuDirectionalCascade {
1391 clip_from_world: cascade.clip_from_world,
1392 texel_size: cascade.texel_size,
1393 far_bound: *bound,
1394 };
1395
1396 let depth_texture_view =
1397 directional_light_depth_texture
1398 .texture
1399 .create_view(&TextureViewDescriptor {
1400 label: Some("directional_light_shadow_map_array_texture_view"),
1401 format: None,
1402 dimension: Some(TextureViewDimension::D2),
1403 aspect: TextureAspect::All,
1404 base_mip_level: 0,
1405 mip_level_count: None,
1406 base_array_layer: directional_depth_texture_array_index,
1407 array_layer_count: Some(1u32),
1408 });
1409
1410 let depth_attachment = DepthAttachment::new(depth_texture_view, Some(0.0));
1414
1415 directional_depth_texture_array_index += 1;
1416
1417 let mut frustum = *frustum;
1418 frustum.half_spaces[4] =
1420 HalfSpace::new(frustum.half_spaces[4].normal().extend(f32::INFINITY));
1421
1422 commands.entity(view_light_entity).insert((
1423 ShadowView {
1424 depth_attachment,
1425 pass_name: format!(
1426 "shadow pass directional light {light_index} cascade {cascade_index}"
1427 ),
1428 },
1429 ExtractedView {
1430 viewport: UVec4::new(
1431 0,
1432 0,
1433 directional_light_shadow_map.size as u32,
1434 directional_light_shadow_map.size as u32,
1435 ),
1436 world_from_view: GlobalTransform::from(cascade.world_from_cascade),
1437 clip_from_view: cascade.clip_from_cascade,
1438 clip_from_world: Some(cascade.clip_from_world),
1439 hdr: false,
1440 color_grading: Default::default(),
1441 },
1442 frustum,
1443 LightEntity::Directional {
1444 light_entity,
1445 cascade_index,
1446 },
1447 ));
1448 view_lights.push(view_light_entity);
1449
1450 shadow_render_phases.insert_or_clear(view_light_entity);
1453 live_shadow_mapping_lights.insert(view_light_entity);
1454 }
1455 }
1456
1457 commands.entity(entity).insert((
1458 ViewShadowBindings {
1459 point_light_depth_texture: point_light_depth_texture.texture.clone(),
1460 point_light_depth_texture_view: point_light_depth_texture_view.clone(),
1461 directional_light_depth_texture: directional_light_depth_texture.texture.clone(),
1462 directional_light_depth_texture_view: directional_light_depth_texture_view.clone(),
1463 },
1464 ViewLightEntities {
1465 lights: view_lights,
1466 },
1467 ViewLightsUniformOffset {
1468 offset: view_gpu_lights_writer.write(&gpu_lights),
1469 },
1470 ));
1471 }
1472
1473 for mut entities in &mut light_view_entities {
1475 for (_, light_view_entities) in
1476 entities.extract_if(|entity, _| !live_views.contains(entity))
1477 {
1478 despawn_entities(&mut commands, light_view_entities);
1479 }
1480 }
1481
1482 shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
1483}
1484
1485fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
1486 if entities.is_empty() {
1487 return;
1488 }
1489 commands.queue(move |world: &mut World| {
1490 for entity in entities {
1491 world.despawn(entity);
1492 }
1493 });
1494}
1495
1496#[allow(clippy::too_many_arguments)]
1500pub fn queue_shadows<M: Material>(
1501 shadow_draw_functions: Res<DrawFunctions<Shadow>>,
1502 prepass_pipeline: Res<PrepassPipeline<M>>,
1503 render_meshes: Res<RenderAssets<RenderMesh>>,
1504 render_mesh_instances: Res<RenderMeshInstances>,
1505 render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
1506 render_material_instances: Res<RenderMaterialInstances<M>>,
1507 mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
1508 mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipeline<M>>>,
1509 pipeline_cache: Res<PipelineCache>,
1510 render_lightmaps: Res<RenderLightmaps>,
1511 view_lights: Query<(Entity, &ViewLightEntities)>,
1512 view_light_entities: Query<&LightEntity>,
1513 point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
1514 directional_light_entities: Query<
1515 &RenderCascadesVisibleEntities,
1516 With<ExtractedDirectionalLight>,
1517 >,
1518 spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
1519) where
1520 M::Data: PartialEq + Eq + Hash + Clone,
1521{
1522 for (entity, view_lights) in &view_lights {
1523 let draw_shadow_mesh = shadow_draw_functions.read().id::<DrawPrepass<M>>();
1524 for view_light_entity in view_lights.lights.iter().copied() {
1525 let Ok(light_entity) = view_light_entities.get(view_light_entity) else {
1526 continue;
1527 };
1528 let Some(shadow_phase) = shadow_render_phases.get_mut(&view_light_entity) else {
1529 continue;
1530 };
1531
1532 let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
1533 let visible_entities = match light_entity {
1534 LightEntity::Directional {
1535 light_entity,
1536 cascade_index,
1537 } => directional_light_entities
1538 .get(*light_entity)
1539 .expect("Failed to get directional light visible entities")
1540 .entities
1541 .get(&entity)
1542 .expect("Failed to get directional light visible entities for view")
1543 .get(*cascade_index)
1544 .expect("Failed to get directional light visible entities for cascade"),
1545 LightEntity::Point {
1546 light_entity,
1547 face_index,
1548 } => point_light_entities
1549 .get(*light_entity)
1550 .expect("Failed to get point light visible entities")
1551 .get(*face_index),
1552 LightEntity::Spot { light_entity } => spot_light_entities
1553 .get(*light_entity)
1554 .expect("Failed to get spot light visible entities"),
1555 };
1556 let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
1557 light_key.set(MeshPipelineKey::DEPTH_CLAMP_ORTHO, is_directional_light);
1558
1559 for (entity, main_entity) in visible_entities.iter().copied() {
1563 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity)
1564 else {
1565 continue;
1566 };
1567 if !mesh_instance
1568 .flags
1569 .contains(RenderMeshInstanceFlags::SHADOW_CASTER)
1570 {
1571 continue;
1572 }
1573 let Some(material_asset_id) = render_material_instances.get(&main_entity) else {
1574 continue;
1575 };
1576 let Some(material) = render_materials.get(*material_asset_id) else {
1577 continue;
1578 };
1579 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
1580 continue;
1581 };
1582
1583 let mut mesh_key =
1584 light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
1585
1586 if render_lightmaps.render_lightmaps.contains_key(&main_entity) {
1592 mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1593 }
1594
1595 mesh_key |= match material.properties.alpha_mode {
1596 AlphaMode::Mask(_)
1597 | AlphaMode::Blend
1598 | AlphaMode::Premultiplied
1599 | AlphaMode::Add
1600 | AlphaMode::AlphaToCoverage => MeshPipelineKey::MAY_DISCARD,
1601 _ => MeshPipelineKey::NONE,
1602 };
1603 let pipeline_id = pipelines.specialize(
1604 &pipeline_cache,
1605 &prepass_pipeline,
1606 MaterialPipelineKey {
1607 mesh_key,
1608 bind_group_data: material.key.clone(),
1609 },
1610 &mesh.layout,
1611 );
1612
1613 let pipeline_id = match pipeline_id {
1614 Ok(id) => id,
1615 Err(err) => {
1616 error!("{}", err);
1617 continue;
1618 }
1619 };
1620
1621 mesh_instance
1622 .material_bind_group_id
1623 .set(material.get_bind_group_id());
1624
1625 shadow_phase.add(
1626 ShadowBinKey {
1627 draw_function: draw_shadow_mesh,
1628 pipeline: pipeline_id,
1629 asset_id: mesh_instance.mesh_asset_id.into(),
1630 },
1631 (entity, main_entity),
1632 BinnedRenderPhaseType::mesh(mesh_instance.should_batch()),
1633 );
1634 }
1635 }
1636 }
1637}
1638
1639pub struct Shadow {
1640 pub key: ShadowBinKey,
1641 pub representative_entity: (Entity, MainEntity),
1642 pub batch_range: Range<u32>,
1643 pub extra_index: PhaseItemExtraIndex,
1644}
1645
1646#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1648pub struct ShadowBinKey {
1649 pub pipeline: CachedRenderPipelineId,
1651
1652 pub draw_function: DrawFunctionId,
1654
1655 pub asset_id: UntypedAssetId,
1657}
1658
1659impl PhaseItem for Shadow {
1660 #[inline]
1661 fn entity(&self) -> Entity {
1662 self.representative_entity.0
1663 }
1664
1665 fn main_entity(&self) -> MainEntity {
1666 self.representative_entity.1
1667 }
1668
1669 #[inline]
1670 fn draw_function(&self) -> DrawFunctionId {
1671 self.key.draw_function
1672 }
1673
1674 #[inline]
1675 fn batch_range(&self) -> &Range<u32> {
1676 &self.batch_range
1677 }
1678
1679 #[inline]
1680 fn batch_range_mut(&mut self) -> &mut Range<u32> {
1681 &mut self.batch_range
1682 }
1683
1684 #[inline]
1685 fn extra_index(&self) -> PhaseItemExtraIndex {
1686 self.extra_index
1687 }
1688
1689 #[inline]
1690 fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
1691 (&mut self.batch_range, &mut self.extra_index)
1692 }
1693}
1694
1695impl BinnedPhaseItem for Shadow {
1696 type BinKey = ShadowBinKey;
1697
1698 #[inline]
1699 fn new(
1700 key: Self::BinKey,
1701 representative_entity: (Entity, MainEntity),
1702 batch_range: Range<u32>,
1703 extra_index: PhaseItemExtraIndex,
1704 ) -> Self {
1705 Shadow {
1706 key,
1707 representative_entity,
1708 batch_range,
1709 extra_index,
1710 }
1711 }
1712}
1713
1714impl CachedRenderPipelinePhaseItem for Shadow {
1715 #[inline]
1716 fn cached_pipeline(&self) -> CachedRenderPipelineId {
1717 self.key.pipeline
1718 }
1719}
1720
1721pub struct ShadowPassNode {
1722 main_view_query: QueryState<Read<ViewLightEntities>>,
1723 view_light_query: QueryState<Read<ShadowView>>,
1724}
1725
1726impl ShadowPassNode {
1727 pub fn new(world: &mut World) -> Self {
1728 Self {
1729 main_view_query: QueryState::new(world),
1730 view_light_query: QueryState::new(world),
1731 }
1732 }
1733}
1734
1735impl Node for ShadowPassNode {
1736 fn update(&mut self, world: &mut World) {
1737 self.main_view_query.update_archetypes(world);
1738 self.view_light_query.update_archetypes(world);
1739 }
1740
1741 fn run<'w>(
1742 &self,
1743 graph: &mut RenderGraphContext,
1744 render_context: &mut RenderContext<'w>,
1745 world: &'w World,
1746 ) -> Result<(), NodeRunError> {
1747 let diagnostics = render_context.diagnostic_recorder();
1748
1749 let view_entity = graph.view_entity();
1750
1751 let Some(shadow_render_phases) = world.get_resource::<ViewBinnedRenderPhases<Shadow>>()
1752 else {
1753 return Ok(());
1754 };
1755
1756 let time_span = diagnostics.time_span(render_context.command_encoder(), "shadows");
1757
1758 if let Ok(view_lights) = self.main_view_query.get_manual(world, view_entity) {
1759 for view_light_entity in view_lights.lights.iter().copied() {
1760 let Some(shadow_phase) = shadow_render_phases.get(&view_light_entity) else {
1761 continue;
1762 };
1763
1764 let view_light = self
1765 .view_light_query
1766 .get_manual(world, view_light_entity)
1767 .unwrap();
1768
1769 let depth_stencil_attachment =
1770 Some(view_light.depth_attachment.get_attachment(StoreOp::Store));
1771
1772 let diagnostics = render_context.diagnostic_recorder();
1773 render_context.add_command_buffer_generation_task(move |render_device| {
1774 #[cfg(feature = "trace")]
1775 let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered();
1776 let mut command_encoder =
1777 render_device.create_command_encoder(&CommandEncoderDescriptor {
1778 label: Some("shadow_pass_command_encoder"),
1779 });
1780
1781 let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
1782 label: Some(&view_light.pass_name),
1783 color_attachments: &[],
1784 depth_stencil_attachment,
1785 timestamp_writes: None,
1786 occlusion_query_set: None,
1787 });
1788
1789 let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
1790 let pass_span =
1791 diagnostics.pass_span(&mut render_pass, view_light.pass_name.clone());
1792
1793 if let Err(err) =
1794 shadow_phase.render(&mut render_pass, world, view_light_entity)
1795 {
1796 error!("Error encountered while rendering the shadow phase {err:?}");
1797 }
1798
1799 pass_span.end(&mut render_pass);
1800 drop(render_pass);
1801 command_encoder.finish()
1802 });
1803 }
1804 }
1805
1806 time_span.end(render_context.command_encoder());
1807
1808 Ok(())
1809 }
1810}