bevy_pbr/render/
light.rs

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