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