bevy_pbr/render/
light.rs

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    /// luminous intensity in lumens per steradian
41    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
70// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
71bitflags::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
104// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
105bitflags::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    // xyz are x/y/z cluster dimensions and w is the number of clusters
120    cluster_dimensions: UVec4,
121    // xy are vec2<f32>(cluster_dimensions.xy) / vec2<f32>(view.width, view.height)
122    // z is cluster_dimensions.z / log(far / near)
123    // w is cluster_dimensions.z * log(near) / log(far / near)
124    cluster_factors: Vec4,
125    n_directional_lights: u32,
126    // offset from spot light's light index to spot light's shadow map index
127    spot_light_shadowmap_offset: i32,
128}
129
130// NOTE: When running bevy on Adreno GPU chipsets in WebGL, any value above 1 will result in a crash
131// when loading the wgsl "pbr_functions.wgsl" in the function apply_fog.
132#[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
159// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
160impl 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    // NOTE: These shadow map resources are extracted here as they are used here too so this avoids
244    // races between scheduling of ExtractResourceSystems and this system.
245    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    // This is the point light shadow map texel size for one face of the cube as a distance of 1.0
252    // world unit from the light.
253    // point_light_texel_size = 2.0 * 1.0 * tan(PI / 4.0) / cube face width in texels
254    // PI / 4.0 is half the cube face fov, tan(PI / 4.0) = 1.0, so this simplifies to:
255    // point_light_texel_size = 2.0 / cube face width in texels
256    // NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to:
257    // https://catlikecoding.com/unity/tutorials/custom-srp/point-and-spot-shadows/
258    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            // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian
289            // for a point light. See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower
290            // for details.
291            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            // The factor of SQRT_2 is for the worst-case diagonal offset
298            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                        // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian
348                        // for a point light. See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower
349                        // for details.
350                        // Note: Filament uses a divisor of PI for spot lights. We choose to use the same 4*PI divisor
351                        // in both cases so that toggling between point light and spot light keeps lit areas lit equally,
352                        // which seems least surprising for users
353                        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                        // The factor of SQRT_2 is for the worst-case diagonal offset
360                        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        // TODO: update in place instead of reinserting.
402        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                    // The factor of SQRT_2 is for the worst-case diagonal offset
448                    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)]
481/// Component automatically attached to a light entity to track light-view entities
482/// for each view.
483pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
484
485// TODO: using required component
486pub(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
495/// Removes [`LightViewEntities`] when light is removed. See [`add_light_view_entities`].
496pub(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
526// Cubemap faces are [+X, -X, +Y, -Y, +Z, -Z], per https://www.w3.org/TR/webgpu/#texture-view-creation
527// Note: Cubemap coordinates are left-handed y-up, unlike the rest of Bevy.
528// See https://registry.khronos.org/vulkan/specs/1.2/html/chap16.html#_cube_map_face_selection
529//
530// For each cubemap face, we take care to specify the appropriate target/up axis such that the rendered
531// texture using Bevy's right-handed y-up coordinate space matches the expected cubemap face in
532// left-handed y-up cubemap coordinates.
533pub(crate) const CUBE_MAP_FACES: [CubeMapFace; 6] = [
534    // +X
535    CubeMapFace {
536        target: Vec3::X,
537        up: Vec3::Y,
538    },
539    // -X
540    CubeMapFace {
541        target: Vec3::NEG_X,
542        up: Vec3::Y,
543    },
544    // +Y
545    CubeMapFace {
546        target: Vec3::Y,
547        up: Vec3::Z,
548    },
549    // -Y
550    CubeMapFace {
551        target: Vec3::NEG_Y,
552        up: Vec3::NEG_Z,
553    },
554    // +Z (with left-handed conventions, pointing forwards)
555    CubeMapFace {
556        target: Vec3::NEG_Z,
557        up: Vec3::Y,
558    },
559    // -Z (with left-handed conventions, pointing backwards)
560    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
638// this method of constructing a basis from a vec3 is used by glam::Vec3::any_orthonormal_pair
639// we will also construct it in the fragment shader and need our implementations to match,
640// so we reproduce it here to avoid a mismatch if glam changes. we also switch the handedness
641// could move this onto transform but it's pretty niche
642pub(crate) fn spot_light_world_from_view(transform: &GlobalTransform) -> Mat4 {
643    // the matrix z_local (opposite of transform.forward())
644    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    // spot light projection FOV is 2x the angle from spot light center to outer edge
667    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    // Pre-calculate for PointLights
716    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    // Sort lights by
815    // - point-light vs spot-light, so that we can iterate point lights and spot lights in contiguous blocks in the fragment shader,
816    // - then those with shadows enabled first, so that the index can be used to render at most `point_light_shadow_maps_count`
817    //   point light shadows and `spot_light_shadow_maps_count` spot light shadow maps,
818    // - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded.
819    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    // Sort lights by
837    // - those with volumetric (and shadows) enabled first, so that the
838    //   volumetric lighting pass can quickly find the volumetric lights;
839    // - then those with shadows enabled second, so that the index can be used
840    //   to render at most `directional_light_shadow_maps_count` directional light
841    //   shadows
842    // - then by entity as a stable key to ensure that a consistent set of
843    //   lights are chosen if the light count limit is exceeded.
844    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        // Lights are sorted, shadow enabled lights are first
862        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                    // For spot lights: the direction (x,z), spot_scale and spot_offset
897                    light_direction.xz().extend(spot_scale).extend(spot_offset),
898                    ops::tan(outer),
899                )
900            }
901            None => {
902                (
903                    // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3]
904                    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                    // unused
911                    0.0,
912                )
913            }
914        };
915
916        gpu_point_lights.push(GpuClusterableObject {
917            light_custom_data,
918            // premultiply color by intensity
919            // we don't use the alpha at all, so no reason to multiply only [0..3]
920            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        // Lights are sorted, volumetric and shadow enabled lights are first
951        if light.volumetric
952            && light.shadows_enabled
953            && (index < directional_volumetric_enabled_count)
954        {
955            flags |= DirectionalLightFlags::VOLUMETRIC;
956        }
957        // Shadow enabled lights are second
958        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            // Set to true later when necessary.
969            skip: 0u32,
970            // Filled in later.
971            cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
972            // premultiply color by illuminance
973            // we don't use the alpha at all, so no reason to multiply only [0..3]
974            color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
975            // direction is negated to be ready for N.L
976            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                // NOTE: iOS Simulator is missing CubeArray support so we use Cube instead.
1027                // See https://github.com/bevyengine/bevy/pull/12052 - remove if support is added.
1028                #[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    // set up light data for each view
1095    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            // spotlight shadow maps are stored in the directional light array, starting at num_directional_cascades_enabled.
1126            // the spot lights themselves start in the light array at point_light_count. so to go from light
1127            // index to shadow map index, we need to subtract point light count and add directional shadowmap count.
1128            spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
1129                - point_light_count as i32,
1130        };
1131
1132        // TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
1133        for &(light_entity, light, (point_light_frusta, _)) in point_lights
1134            .iter()
1135            // Lights are sorted, shadow enabled lights are first
1136            .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            // ignore scale because we don't want to effectively scale light radius and range
1154            // by applying those as a view transform to shadow map rendering of objects
1155            // and ignore rotation because we want the shadow map projections to align with the axes
1156            let view_translation = GlobalTransform::from_translation(light.transform.translation());
1157
1158            // for each face of a cube and each view we spawn a light entity
1159            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                    // Subsequent views with the same light entity will reuse the same shadow map
1234                    shadow_render_phases.insert_or_clear(view_light_entity);
1235                    live_shadow_mapping_lights.insert(view_light_entity);
1236                }
1237            }
1238        }
1239
1240        // spot lights
1241        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                // Subsequent views with the same light entity will reuse the same shadow map
1322                shadow_render_phases.insert_or_clear(view_light_entity);
1323                live_shadow_mapping_lights.insert(view_light_entity);
1324            }
1325        }
1326
1327        // directional lights
1328        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            // Check if the light intersects with the view.
1342            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            // Only deal with cascades when shadows are enabled.
1351            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                // NOTE: For point and spotlights, we reuse the same depth attachment for all views.
1411                // However, for directional lights, we want a new depth attachment for each view,
1412                // so that the view is cleared for each view.
1413                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                // Push the near clip plane out to infinity for directional lights
1419                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                // Subsequent views with the same light entity will **NOT** reuse the same shadow map
1451                // (Because the cascades are unique to each view)
1452                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    // Despawn light-view entities for views that no longer exist
1474    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/// For each shadow cascade, iterates over all the meshes "visible" from it and
1497/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
1498/// appropriate.
1499#[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            // NOTE: Lights with shadow mapping disabled will have no visible entities
1560            // so no meshes will be queued
1561
1562            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                // Even though we don't use the lightmap in the shadow map, the
1587                // `SetMeshBindGroup` render command will bind the data for it. So
1588                // we need to include the appropriate flag in the mesh pipeline key
1589                // to ensure that the necessary bind group layout entries are
1590                // present.
1591                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/// Data used to bin each object in the shadow map phase.
1647#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1648pub struct ShadowBinKey {
1649    /// The identifier of the render pipeline.
1650    pub pipeline: CachedRenderPipelineId,
1651
1652    /// The function used to draw.
1653    pub draw_function: DrawFunctionId,
1654
1655    /// The object.
1656    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}