Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/render/light.rs
6600 views
1
use crate::*;
2
use bevy_asset::UntypedAssetId;
3
use bevy_camera::primitives::{
4
face_index_to_name, CascadesFrusta, CubeMapFace, CubemapFrusta, Frustum, HalfSpace,
5
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::component::Tick;
16
use bevy_ecs::system::SystemChangeTick;
17
use bevy_ecs::{
18
entity::{EntityHashMap, EntityHashSet},
19
prelude::*,
20
system::lifetimeless::Read,
21
};
22
use bevy_light::cascade::Cascade;
23
use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType};
24
use bevy_light::cluster::GlobalVisibleClusterableObjects;
25
use bevy_light::SunDisk;
26
use bevy_light::{
27
spot_light_clip_from_view, spot_light_world_from_view, AmbientLight, CascadeShadowConfig,
28
Cascades, DirectionalLight, DirectionalLightShadowMap, NotShadowCaster, PointLight,
29
PointLightShadowMap, ShadowFilteringMethod, SpotLight, VolumetricLight,
30
};
31
use bevy_math::{ops, Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
32
use bevy_platform::collections::{HashMap, HashSet};
33
use bevy_platform::hash::FixedHasher;
34
use bevy_render::erased_render_asset::ErasedRenderAssets;
35
use bevy_render::experimental::occlusion_culling::{
36
OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
37
};
38
use bevy_render::sync_world::MainEntityHashMap;
39
use bevy_render::{
40
batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
41
camera::SortedCameras,
42
mesh::allocator::MeshAllocator,
43
view::{NoIndirectDrawing, RetainedViewEntity},
44
};
45
use 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
};
57
use bevy_render::{
58
mesh::allocator::SlabId,
59
sync_world::{MainEntity, RenderEntity},
60
};
61
use bevy_transform::{components::GlobalTransform, prelude::Transform};
62
use bevy_utils::default;
63
use core::{hash::Hash, ops::Range};
64
use decal::clustered::RenderClusteredDecals;
65
#[cfg(feature = "trace")]
66
use tracing::info_span;
67
use tracing::{error, warn};
68
69
#[derive(Component)]
70
pub 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)]
89
pub 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!
112
bitflags::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)]
125
pub struct GpuDirectionalCascade {
126
clip_from_world: Mat4,
127
texel_size: f32,
128
far_bound: f32,
129
}
130
131
#[derive(Copy, Clone, ShaderType, Default, Debug)]
132
pub 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!
149
bitflags::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)]
161
pub 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")))]
179
pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
180
#[cfg(any(
181
not(feature = "webgl"),
182
not(target_arch = "wasm32"),
183
feature = "webgpu"
184
))]
185
pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
186
#[cfg(any(
187
not(feature = "webgl"),
188
not(target_arch = "wasm32"),
189
feature = "webgpu"
190
))]
191
pub const MAX_CASCADES_PER_LIGHT: usize = 4;
192
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
193
pub const MAX_CASCADES_PER_LIGHT: usize = 1;
194
195
#[derive(Resource, Clone)]
196
pub 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
205
pub 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
234
pub 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
249
pub fn extract_ambient_light_resource(
250
mut commands: Commands,
251
main_resource: Extract<Option<Res<AmbientLight>>>,
252
target_resource: Option<ResMut<AmbientLight>>,
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
267
pub 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
280
pub 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
595
fn 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.
613
pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
614
615
// TODO: using required component
616
pub(crate) fn add_light_view_entities(
617
event: On<Add, (ExtractedDirectionalLight, ExtractedPointLight)>,
618
mut commands: Commands,
619
) {
620
if let Ok(mut v) = commands.get_entity(event.entity()) {
621
v.insert(LightViewEntities::default());
622
}
623
}
624
625
/// Removes [`LightViewEntities`] when light is removed. See [`add_light_view_entities`].
626
pub(crate) fn extracted_light_removed(
627
event: On<Remove, (ExtractedDirectionalLight, ExtractedPointLight)>,
628
mut commands: Commands,
629
) {
630
if let Ok(mut v) = commands.get_entity(event.entity()) {
631
v.try_remove::<LightViewEntities>();
632
}
633
}
634
635
pub(crate) fn remove_light_view_entities(
636
event: On<Remove, LightViewEntities>,
637
query: Query<&LightViewEntities>,
638
mut commands: Commands,
639
) {
640
if let Ok(entities) = query.get(event.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)]
652
pub struct ShadowView {
653
pub depth_attachment: DepthAttachment,
654
pub pass_name: String,
655
}
656
657
#[derive(Component)]
658
pub 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)]
671
pub 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)]
681
pub struct ViewLightsUniformOffset {
682
pub offset: u32,
683
}
684
685
#[derive(Resource, Default)]
686
pub struct LightMeta {
687
pub view_gpu_lights: DynamicUniformBuffer<GpuLights>,
688
}
689
690
#[derive(Component)]
691
pub 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
705
pub 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<AmbientLight>,
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 = maybe_ambient_override.unwrap_or(&ambient_light);
1154
1155
let mut gpu_directional_lights = [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS];
1156
let mut num_directional_cascades_enabled_for_this_view = 0usize;
1157
let mut num_directional_lights_for_this_view = 0usize;
1158
for (index, (light_entity, _, light)) in directional_lights
1159
.iter()
1160
.filter(|(_light_entity, _, light)| light.render_layers.intersects(view_layers))
1161
.enumerate()
1162
.take(MAX_DIRECTIONAL_LIGHTS)
1163
{
1164
num_directional_lights_for_this_view += 1;
1165
1166
let mut flags = DirectionalLightFlags::NONE;
1167
1168
// Lights are sorted, volumetric and shadow enabled lights are first
1169
if light.volumetric
1170
&& light.shadows_enabled
1171
&& (index < directional_volumetric_enabled_count)
1172
{
1173
flags |= DirectionalLightFlags::VOLUMETRIC;
1174
}
1175
1176
// Shadow enabled lights are second
1177
let mut num_cascades = 0;
1178
if light.shadows_enabled {
1179
let cascades = light
1180
.cascade_shadow_config
1181
.bounds
1182
.len()
1183
.min(MAX_CASCADES_PER_LIGHT);
1184
1185
if num_directional_cascades_enabled_for_this_view + cascades
1186
<= max_texture_array_layers
1187
{
1188
flags |= DirectionalLightFlags::SHADOWS_ENABLED;
1189
num_cascades += cascades;
1190
}
1191
}
1192
1193
if light.affects_lightmapped_mesh_diffuse {
1194
flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE;
1195
}
1196
1197
gpu_directional_lights[index] = GpuDirectionalLight {
1198
// Filled in later.
1199
cascades: [GpuDirectionalCascade::default(); MAX_CASCADES_PER_LIGHT],
1200
// premultiply color by illuminance
1201
// we don't use the alpha at all, so no reason to multiply only [0..3]
1202
color: Vec4::from_slice(&light.color.to_f32_array()) * light.illuminance,
1203
// direction is negated to be ready for N.L
1204
dir_to_light: light.transform.back().into(),
1205
flags: flags.bits(),
1206
soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
1207
shadow_depth_bias: light.shadow_depth_bias,
1208
shadow_normal_bias: light.shadow_normal_bias,
1209
num_cascades: num_cascades as u32,
1210
cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
1211
depth_texture_base_index: num_directional_cascades_enabled_for_this_view as u32,
1212
sun_disk_angular_size: light.sun_disk_angular_size,
1213
sun_disk_intensity: light.sun_disk_intensity,
1214
decal_index: decals
1215
.as_ref()
1216
.and_then(|decals| decals.get(*light_entity))
1217
.and_then(|index| index.try_into().ok())
1218
.unwrap_or(u32::MAX),
1219
};
1220
num_directional_cascades_enabled_for_this_view += num_cascades;
1221
}
1222
1223
let mut gpu_lights = GpuLights {
1224
directional_lights: gpu_directional_lights,
1225
ambient_color: Vec4::from_slice(&LinearRgba::from(ambient_light.color).to_f32_array())
1226
* ambient_light.brightness,
1227
cluster_factors: Vec4::new(
1228
clusters.dimensions.x as f32 / extracted_view.viewport.z as f32,
1229
clusters.dimensions.y as f32 / extracted_view.viewport.w as f32,
1230
cluster_factors_zw.x,
1231
cluster_factors_zw.y,
1232
),
1233
cluster_dimensions: clusters.dimensions.extend(n_clusters),
1234
n_directional_lights: num_directional_lights_for_this_view as u32,
1235
// spotlight shadow maps are stored in the directional light array, starting at num_directional_cascades_enabled.
1236
// the spot lights themselves start in the light array at point_light_count. so to go from light
1237
// index to shadow map index, we need to subtract point light count and add directional shadowmap count.
1238
spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
1239
- point_light_count as i32,
1240
ambient_light_affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes
1241
as u32,
1242
};
1243
1244
// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
1245
for &(light_entity, light_main_entity, light, (point_light_frusta, _)) in point_lights
1246
.iter()
1247
// Lights are sorted, shadow enabled lights are first
1248
.take(point_light_count.min(max_texture_cubes))
1249
{
1250
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1251
continue;
1252
};
1253
1254
if !light.shadows_enabled {
1255
if let Some(entities) = light_view_entities.remove(&entity) {
1256
despawn_entities(&mut commands, entities);
1257
}
1258
continue;
1259
}
1260
1261
let light_index = *global_light_meta
1262
.entity_to_index
1263
.get(&light_entity)
1264
.unwrap();
1265
// ignore scale because we don't want to effectively scale light radius and range
1266
// by applying those as a view transform to shadow map rendering of objects
1267
// and ignore rotation because we want the shadow map projections to align with the axes
1268
let view_translation = GlobalTransform::from_translation(light.transform.translation());
1269
1270
// for each face of a cube and each view we spawn a light entity
1271
let light_view_entities = light_view_entities
1272
.entry(entity)
1273
.or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect());
1274
1275
let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
1276
core::f32::consts::FRAC_PI_2,
1277
1.0,
1278
light.shadow_map_near_z,
1279
);
1280
1281
for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
1282
.iter()
1283
.zip(&point_light_frusta.unwrap().frusta)
1284
.zip(light_view_entities.iter().copied())
1285
.enumerate()
1286
{
1287
let mut first = false;
1288
let base_array_layer = (light_index * 6 + face_index) as u32;
1289
1290
let depth_attachment = point_light_depth_attachments
1291
.entry(base_array_layer)
1292
.or_insert_with(|| {
1293
first = true;
1294
1295
let depth_texture_view =
1296
point_light_depth_texture
1297
.texture
1298
.create_view(&TextureViewDescriptor {
1299
label: Some("point_light_shadow_map_texture_view"),
1300
format: None,
1301
dimension: Some(TextureViewDimension::D2),
1302
usage: None,
1303
aspect: TextureAspect::All,
1304
base_mip_level: 0,
1305
mip_level_count: None,
1306
base_array_layer,
1307
array_layer_count: Some(1u32),
1308
});
1309
1310
DepthAttachment::new(depth_texture_view, Some(0.0))
1311
})
1312
.clone();
1313
1314
let retained_view_entity = RetainedViewEntity::new(
1315
*light_main_entity,
1316
Some(camera_main_entity.into()),
1317
face_index as u32,
1318
);
1319
1320
commands.entity(view_light_entity).insert((
1321
ShadowView {
1322
depth_attachment,
1323
pass_name: format!(
1324
"shadow_point_light_{}_{}",
1325
light_index,
1326
face_index_to_name(face_index)
1327
),
1328
},
1329
ExtractedView {
1330
retained_view_entity,
1331
viewport: UVec4::new(
1332
0,
1333
0,
1334
point_light_shadow_map.size as u32,
1335
point_light_shadow_map.size as u32,
1336
),
1337
world_from_view: view_translation * *view_rotation,
1338
clip_from_world: None,
1339
clip_from_view: cube_face_projection,
1340
hdr: false,
1341
color_grading: Default::default(),
1342
},
1343
*frustum,
1344
LightEntity::Point {
1345
light_entity,
1346
face_index,
1347
},
1348
));
1349
1350
if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1351
commands.entity(view_light_entity).insert(NoIndirectDrawing);
1352
}
1353
1354
view_lights.push(view_light_entity);
1355
1356
if first {
1357
// Subsequent views with the same light entity will reuse the same shadow map
1358
shadow_render_phases
1359
.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1360
live_shadow_mapping_lights.insert(retained_view_entity);
1361
}
1362
}
1363
}
1364
1365
// spot lights
1366
for (light_index, &(light_entity, light_main_entity, light, (_, spot_light_frustum))) in
1367
point_lights
1368
.iter()
1369
.skip(point_light_count)
1370
.take(spot_light_count)
1371
.enumerate()
1372
{
1373
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1374
continue;
1375
};
1376
1377
if !light.shadows_enabled {
1378
if let Some(entities) = light_view_entities.remove(&entity) {
1379
despawn_entities(&mut commands, entities);
1380
}
1381
continue;
1382
}
1383
1384
let spot_world_from_view = spot_light_world_from_view(&light.transform);
1385
let spot_world_from_view = spot_world_from_view.into();
1386
1387
let angle = light.spot_light_angles.expect("lights should be sorted so that \
1388
[point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
1389
let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
1390
1391
let mut first = false;
1392
let base_array_layer = (num_directional_cascades_enabled + light_index) as u32;
1393
1394
let depth_attachment = directional_light_depth_attachments
1395
.entry(base_array_layer)
1396
.or_insert_with(|| {
1397
first = true;
1398
1399
let depth_texture_view = directional_light_depth_texture.texture.create_view(
1400
&TextureViewDescriptor {
1401
label: Some("spot_light_shadow_map_texture_view"),
1402
format: None,
1403
dimension: Some(TextureViewDimension::D2),
1404
usage: None,
1405
aspect: TextureAspect::All,
1406
base_mip_level: 0,
1407
mip_level_count: None,
1408
base_array_layer,
1409
array_layer_count: Some(1u32),
1410
},
1411
);
1412
1413
DepthAttachment::new(depth_texture_view, Some(0.0))
1414
})
1415
.clone();
1416
1417
let light_view_entities = light_view_entities
1418
.entry(entity)
1419
.or_insert_with(|| vec![commands.spawn_empty().id()]);
1420
1421
let view_light_entity = light_view_entities[0];
1422
1423
let retained_view_entity =
1424
RetainedViewEntity::new(*light_main_entity, Some(camera_main_entity.into()), 0);
1425
1426
commands.entity(view_light_entity).insert((
1427
ShadowView {
1428
depth_attachment,
1429
pass_name: format!("shadow_spot_light_{light_index}"),
1430
},
1431
ExtractedView {
1432
retained_view_entity,
1433
viewport: UVec4::new(
1434
0,
1435
0,
1436
directional_light_shadow_map.size as u32,
1437
directional_light_shadow_map.size as u32,
1438
),
1439
world_from_view: spot_world_from_view,
1440
clip_from_view: spot_projection,
1441
clip_from_world: None,
1442
hdr: false,
1443
color_grading: Default::default(),
1444
},
1445
*spot_light_frustum.unwrap(),
1446
LightEntity::Spot { light_entity },
1447
));
1448
1449
if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1450
commands.entity(view_light_entity).insert(NoIndirectDrawing);
1451
}
1452
1453
view_lights.push(view_light_entity);
1454
1455
if first {
1456
// Subsequent views with the same light entity will reuse the same shadow map
1457
shadow_render_phases
1458
.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1459
live_shadow_mapping_lights.insert(retained_view_entity);
1460
}
1461
}
1462
1463
// directional lights
1464
// clear entities for lights that don't intersect the layer
1465
for &(light_entity, _, _) in directional_lights
1466
.iter()
1467
.filter(|(_, _, light)| !light.render_layers.intersects(view_layers))
1468
{
1469
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1470
continue;
1471
};
1472
if let Some(entities) = light_view_entities.remove(&entity) {
1473
despawn_entities(&mut commands, entities);
1474
}
1475
}
1476
1477
let mut directional_depth_texture_array_index = 0u32;
1478
for (light_index, &(light_entity, light_main_entity, light)) in directional_lights
1479
.iter()
1480
.filter(|(_, _, light)| light.render_layers.intersects(view_layers))
1481
.enumerate()
1482
.take(MAX_DIRECTIONAL_LIGHTS)
1483
{
1484
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
1485
continue;
1486
};
1487
1488
let gpu_light = &mut gpu_lights.directional_lights[light_index];
1489
1490
// Only deal with cascades when shadows are enabled.
1491
if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
1492
if let Some(entities) = light_view_entities.remove(&entity) {
1493
despawn_entities(&mut commands, entities);
1494
}
1495
continue;
1496
}
1497
1498
let cascades = light
1499
.cascades
1500
.get(&entity)
1501
.unwrap()
1502
.iter()
1503
.take(MAX_CASCADES_PER_LIGHT);
1504
let frusta = light
1505
.frusta
1506
.get(&entity)
1507
.unwrap()
1508
.iter()
1509
.take(MAX_CASCADES_PER_LIGHT);
1510
1511
let iter = cascades
1512
.zip(frusta)
1513
.zip(&light.cascade_shadow_config.bounds);
1514
1515
let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
1516
(0..iter.len())
1517
.map(|_| commands.spawn_empty().id())
1518
.collect()
1519
});
1520
if light_view_entities.len() != iter.len() {
1521
let entities = core::mem::take(light_view_entities);
1522
despawn_entities(&mut commands, entities);
1523
light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
1524
}
1525
1526
for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
1527
iter.zip(light_view_entities.iter().copied()).enumerate()
1528
{
1529
gpu_lights.directional_lights[light_index].cascades[cascade_index] =
1530
GpuDirectionalCascade {
1531
clip_from_world: cascade.clip_from_world,
1532
texel_size: cascade.texel_size,
1533
far_bound: *bound,
1534
};
1535
1536
let depth_texture_view =
1537
directional_light_depth_texture
1538
.texture
1539
.create_view(&TextureViewDescriptor {
1540
label: Some("directional_light_shadow_map_array_texture_view"),
1541
format: None,
1542
dimension: Some(TextureViewDimension::D2),
1543
usage: None,
1544
aspect: TextureAspect::All,
1545
base_mip_level: 0,
1546
mip_level_count: None,
1547
base_array_layer: directional_depth_texture_array_index,
1548
array_layer_count: Some(1u32),
1549
});
1550
1551
// NOTE: For point and spotlights, we reuse the same depth attachment for all views.
1552
// However, for directional lights, we want a new depth attachment for each view,
1553
// so that the view is cleared for each view.
1554
let depth_attachment = DepthAttachment::new(depth_texture_view.clone(), Some(0.0));
1555
1556
directional_depth_texture_array_index += 1;
1557
1558
let mut frustum = *frustum;
1559
// Push the near clip plane out to infinity for directional lights
1560
frustum.half_spaces[4] =
1561
HalfSpace::new(frustum.half_spaces[4].normal().extend(f32::INFINITY));
1562
1563
let retained_view_entity = RetainedViewEntity::new(
1564
*light_main_entity,
1565
Some(camera_main_entity.into()),
1566
cascade_index as u32,
1567
);
1568
1569
commands.entity(view_light_entity).insert((
1570
ShadowView {
1571
depth_attachment,
1572
pass_name: format!(
1573
"shadow_directional_light_{light_index}_cascade_{cascade_index}"
1574
),
1575
},
1576
ExtractedView {
1577
retained_view_entity,
1578
viewport: UVec4::new(
1579
0,
1580
0,
1581
directional_light_shadow_map.size as u32,
1582
directional_light_shadow_map.size as u32,
1583
),
1584
world_from_view: GlobalTransform::from(cascade.world_from_cascade),
1585
clip_from_view: cascade.clip_from_cascade,
1586
clip_from_world: Some(cascade.clip_from_world),
1587
hdr: false,
1588
color_grading: Default::default(),
1589
},
1590
frustum,
1591
LightEntity::Directional {
1592
light_entity,
1593
cascade_index,
1594
},
1595
));
1596
1597
if !matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) {
1598
commands.entity(view_light_entity).insert(NoIndirectDrawing);
1599
}
1600
1601
view_lights.push(view_light_entity);
1602
1603
// If this light is using occlusion culling, add the appropriate components.
1604
if light.occlusion_culling {
1605
commands.entity(view_light_entity).insert((
1606
OcclusionCulling,
1607
OcclusionCullingSubview {
1608
depth_texture_view,
1609
depth_texture_size: directional_light_shadow_map.size as u32,
1610
},
1611
));
1612
view_occlusion_culling_lights.push(view_light_entity);
1613
}
1614
1615
// Subsequent views with the same light entity will **NOT** reuse the same shadow map
1616
// (Because the cascades are unique to each view)
1617
// TODO: Implement GPU culling for shadow passes.
1618
shadow_render_phases
1619
.prepare_for_new_frame(retained_view_entity, gpu_preprocessing_mode);
1620
live_shadow_mapping_lights.insert(retained_view_entity);
1621
}
1622
}
1623
1624
commands.entity(entity).insert((
1625
ViewShadowBindings {
1626
point_light_depth_texture: point_light_depth_texture.texture.clone(),
1627
point_light_depth_texture_view: point_light_depth_texture_view.clone(),
1628
directional_light_depth_texture: directional_light_depth_texture.texture.clone(),
1629
directional_light_depth_texture_view: directional_light_depth_texture_view.clone(),
1630
},
1631
ViewLightEntities {
1632
lights: view_lights,
1633
},
1634
ViewLightsUniformOffset {
1635
offset: view_gpu_lights_writer.write(&gpu_lights),
1636
},
1637
));
1638
1639
// Make a link from the camera to all shadow cascades with occlusion
1640
// culling enabled.
1641
if !view_occlusion_culling_lights.is_empty() {
1642
commands
1643
.entity(entity)
1644
.insert(OcclusionCullingSubviewEntities(
1645
view_occlusion_culling_lights,
1646
));
1647
}
1648
}
1649
1650
// Despawn light-view entities for views that no longer exist
1651
for mut entities in &mut light_view_entities {
1652
for (_, light_view_entities) in
1653
entities.extract_if(|entity, _| !live_views.contains(entity))
1654
{
1655
despawn_entities(&mut commands, light_view_entities);
1656
}
1657
}
1658
1659
shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
1660
}
1661
1662
fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
1663
if entities.is_empty() {
1664
return;
1665
}
1666
commands.queue(move |world: &mut World| {
1667
for entity in entities {
1668
world.despawn(entity);
1669
}
1670
});
1671
}
1672
1673
// These will be extracted in the material extraction, which will also clear the needs_specialization
1674
// collection.
1675
pub fn check_light_entities_needing_specialization<M: Material>(
1676
needs_specialization: Query<Entity, (With<MeshMaterial3d<M>>, Changed<NotShadowCaster>)>,
1677
mut entities_needing_specialization: ResMut<EntitiesNeedingSpecialization<M>>,
1678
mut removed_components: RemovedComponents<NotShadowCaster>,
1679
) {
1680
for entity in &needs_specialization {
1681
entities_needing_specialization.push(entity);
1682
}
1683
1684
for removed in removed_components.read() {
1685
entities_needing_specialization.entities.push(removed);
1686
}
1687
}
1688
1689
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
1690
pub struct LightKeyCache(HashMap<RetainedViewEntity, MeshPipelineKey>);
1691
1692
#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]
1693
pub struct LightSpecializationTicks(HashMap<RetainedViewEntity, Tick>);
1694
1695
#[derive(Resource, Deref, DerefMut, Default)]
1696
pub struct SpecializedShadowMaterialPipelineCache {
1697
// view light entity -> view pipeline cache
1698
#[deref]
1699
map: HashMap<RetainedViewEntity, SpecializedShadowMaterialViewPipelineCache>,
1700
}
1701
1702
#[derive(Deref, DerefMut, Default)]
1703
pub struct SpecializedShadowMaterialViewPipelineCache {
1704
#[deref]
1705
map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
1706
}
1707
1708
pub fn check_views_lights_need_specialization(
1709
view_lights: Query<&ViewLightEntities, With<ExtractedView>>,
1710
view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1711
shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
1712
mut light_key_cache: ResMut<LightKeyCache>,
1713
mut light_specialization_ticks: ResMut<LightSpecializationTicks>,
1714
ticks: SystemChangeTick,
1715
) {
1716
for view_lights in &view_lights {
1717
for view_light_entity in view_lights.lights.iter().copied() {
1718
let Ok((light_entity, extracted_view_light)) =
1719
view_light_entities.get(view_light_entity)
1720
else {
1721
continue;
1722
};
1723
if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
1724
continue;
1725
}
1726
1727
let is_directional_light = matches!(light_entity, LightEntity::Directional { .. });
1728
let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
1729
light_key.set(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO, is_directional_light);
1730
if let Some(current_key) =
1731
light_key_cache.get_mut(&extracted_view_light.retained_view_entity)
1732
{
1733
if *current_key != light_key {
1734
light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
1735
light_specialization_ticks
1736
.insert(extracted_view_light.retained_view_entity, ticks.this_run());
1737
}
1738
} else {
1739
light_key_cache.insert(extracted_view_light.retained_view_entity, light_key);
1740
light_specialization_ticks
1741
.insert(extracted_view_light.retained_view_entity, ticks.this_run());
1742
}
1743
}
1744
}
1745
}
1746
1747
pub fn specialize_shadows(
1748
prepass_pipeline: Res<PrepassPipeline>,
1749
(render_meshes, render_mesh_instances, render_materials, render_material_instances): (
1750
Res<RenderAssets<RenderMesh>>,
1751
Res<RenderMeshInstances>,
1752
Res<ErasedRenderAssets<PreparedMaterial>>,
1753
Res<RenderMaterialInstances>,
1754
),
1755
shadow_render_phases: Res<ViewBinnedRenderPhases<Shadow>>,
1756
mut pipelines: ResMut<SpecializedMeshPipelines<PrepassPipelineSpecializer>>,
1757
pipeline_cache: Res<PipelineCache>,
1758
render_lightmaps: Res<RenderLightmaps>,
1759
view_lights: Query<(Entity, &ViewLightEntities), With<ExtractedView>>,
1760
view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1761
point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
1762
directional_light_entities: Query<
1763
&RenderCascadesVisibleEntities,
1764
With<ExtractedDirectionalLight>,
1765
>,
1766
spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
1767
light_key_cache: Res<LightKeyCache>,
1768
mut specialized_material_pipeline_cache: ResMut<SpecializedShadowMaterialPipelineCache>,
1769
light_specialization_ticks: Res<LightSpecializationTicks>,
1770
entity_specialization_ticks: Res<EntitySpecializationTicks>,
1771
ticks: SystemChangeTick,
1772
) {
1773
// Record the retained IDs of all shadow views so that we can expire old
1774
// pipeline IDs.
1775
let mut all_shadow_views: HashSet<RetainedViewEntity, FixedHasher> = HashSet::default();
1776
1777
for (entity, view_lights) in &view_lights {
1778
for view_light_entity in view_lights.lights.iter().copied() {
1779
let Ok((light_entity, extracted_view_light)) =
1780
view_light_entities.get(view_light_entity)
1781
else {
1782
continue;
1783
};
1784
1785
all_shadow_views.insert(extracted_view_light.retained_view_entity);
1786
1787
if !shadow_render_phases.contains_key(&extracted_view_light.retained_view_entity) {
1788
continue;
1789
}
1790
let Some(light_key) = light_key_cache.get(&extracted_view_light.retained_view_entity)
1791
else {
1792
continue;
1793
};
1794
1795
let visible_entities = match light_entity {
1796
LightEntity::Directional {
1797
light_entity,
1798
cascade_index,
1799
} => directional_light_entities
1800
.get(*light_entity)
1801
.expect("Failed to get directional light visible entities")
1802
.entities
1803
.get(&entity)
1804
.expect("Failed to get directional light visible entities for view")
1805
.get(*cascade_index)
1806
.expect("Failed to get directional light visible entities for cascade"),
1807
LightEntity::Point {
1808
light_entity,
1809
face_index,
1810
} => point_light_entities
1811
.get(*light_entity)
1812
.expect("Failed to get point light visible entities")
1813
.get(*face_index),
1814
LightEntity::Spot { light_entity } => spot_light_entities
1815
.get(*light_entity)
1816
.expect("Failed to get spot light visible entities"),
1817
};
1818
1819
// NOTE: Lights with shadow mapping disabled will have no visible entities
1820
// so no meshes will be queued
1821
1822
let view_tick = light_specialization_ticks
1823
.get(&extracted_view_light.retained_view_entity)
1824
.unwrap();
1825
let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
1826
.entry(extracted_view_light.retained_view_entity)
1827
.or_default();
1828
1829
for (_, visible_entity) in visible_entities.iter().copied() {
1830
let Some(material_instance) =
1831
render_material_instances.instances.get(&visible_entity)
1832
else {
1833
continue;
1834
};
1835
1836
let Some(mesh_instance) =
1837
render_mesh_instances.render_mesh_queue_data(visible_entity)
1838
else {
1839
continue;
1840
};
1841
let entity_tick = entity_specialization_ticks.get(&visible_entity).unwrap();
1842
let last_specialized_tick = view_specialized_material_pipeline_cache
1843
.get(&visible_entity)
1844
.map(|(tick, _)| *tick);
1845
let needs_specialization = last_specialized_tick.is_none_or(|tick| {
1846
view_tick.is_newer_than(tick, ticks.this_run())
1847
|| entity_tick.is_newer_than(tick, ticks.this_run())
1848
});
1849
if !needs_specialization {
1850
continue;
1851
}
1852
let Some(material) = render_materials.get(material_instance.asset_id) else {
1853
continue;
1854
};
1855
if !material.properties.shadows_enabled {
1856
// If the material is not a shadow caster, we don't need to specialize it.
1857
continue;
1858
}
1859
if !mesh_instance
1860
.flags
1861
.contains(RenderMeshInstanceFlags::SHADOW_CASTER)
1862
{
1863
continue;
1864
}
1865
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
1866
continue;
1867
};
1868
1869
let mut mesh_key =
1870
*light_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
1871
1872
// Even though we don't use the lightmap in the shadow map, the
1873
// `SetMeshBindGroup` render command will bind the data for it. So
1874
// we need to include the appropriate flag in the mesh pipeline key
1875
// to ensure that the necessary bind group layout entries are
1876
// present.
1877
if render_lightmaps
1878
.render_lightmaps
1879
.contains_key(&visible_entity)
1880
{
1881
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1882
}
1883
1884
mesh_key |= match material.properties.alpha_mode {
1885
AlphaMode::Mask(_)
1886
| AlphaMode::Blend
1887
| AlphaMode::Premultiplied
1888
| AlphaMode::Add
1889
| AlphaMode::AlphaToCoverage => MeshPipelineKey::MAY_DISCARD,
1890
_ => MeshPipelineKey::NONE,
1891
};
1892
let erased_key = ErasedMaterialPipelineKey {
1893
mesh_key,
1894
material_key: material.properties.material_key.clone(),
1895
type_id: material_instance.asset_id.type_id(),
1896
};
1897
let material_pipeline_specializer = PrepassPipelineSpecializer {
1898
pipeline: prepass_pipeline.clone(),
1899
properties: material.properties.clone(),
1900
};
1901
let pipeline_id = pipelines.specialize(
1902
&pipeline_cache,
1903
&material_pipeline_specializer,
1904
erased_key,
1905
&mesh.layout,
1906
);
1907
let pipeline_id = match pipeline_id {
1908
Ok(id) => id,
1909
Err(err) => {
1910
error!("{}", err);
1911
continue;
1912
}
1913
};
1914
1915
view_specialized_material_pipeline_cache
1916
.insert(visible_entity, (ticks.this_run(), pipeline_id));
1917
}
1918
}
1919
}
1920
1921
// Delete specialized pipelines belonging to views that have expired.
1922
specialized_material_pipeline_cache.retain(|view, _| all_shadow_views.contains(view));
1923
}
1924
1925
/// For each shadow cascade, iterates over all the meshes "visible" from it and
1926
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
1927
/// appropriate.
1928
pub fn queue_shadows(
1929
render_mesh_instances: Res<RenderMeshInstances>,
1930
render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
1931
render_material_instances: Res<RenderMaterialInstances>,
1932
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
1933
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1934
mesh_allocator: Res<MeshAllocator>,
1935
view_lights: Query<(Entity, &ViewLightEntities, Option<&RenderLayers>), With<ExtractedView>>,
1936
view_light_entities: Query<(&LightEntity, &ExtractedView)>,
1937
point_light_entities: Query<&RenderCubemapVisibleEntities, With<ExtractedPointLight>>,
1938
directional_light_entities: Query<
1939
&RenderCascadesVisibleEntities,
1940
With<ExtractedDirectionalLight>,
1941
>,
1942
spot_light_entities: Query<&RenderVisibleMeshEntities, With<ExtractedPointLight>>,
1943
specialized_material_pipeline_cache: Res<SpecializedShadowMaterialPipelineCache>,
1944
) {
1945
for (entity, view_lights, camera_layers) in &view_lights {
1946
for view_light_entity in view_lights.lights.iter().copied() {
1947
let Ok((light_entity, extracted_view_light)) =
1948
view_light_entities.get(view_light_entity)
1949
else {
1950
continue;
1951
};
1952
let Some(shadow_phase) =
1953
shadow_render_phases.get_mut(&extracted_view_light.retained_view_entity)
1954
else {
1955
continue;
1956
};
1957
1958
let Some(view_specialized_material_pipeline_cache) =
1959
specialized_material_pipeline_cache.get(&extracted_view_light.retained_view_entity)
1960
else {
1961
continue;
1962
};
1963
1964
let visible_entities = match light_entity {
1965
LightEntity::Directional {
1966
light_entity,
1967
cascade_index,
1968
} => directional_light_entities
1969
.get(*light_entity)
1970
.expect("Failed to get directional light visible entities")
1971
.entities
1972
.get(&entity)
1973
.expect("Failed to get directional light visible entities for view")
1974
.get(*cascade_index)
1975
.expect("Failed to get directional light visible entities for cascade"),
1976
LightEntity::Point {
1977
light_entity,
1978
face_index,
1979
} => point_light_entities
1980
.get(*light_entity)
1981
.expect("Failed to get point light visible entities")
1982
.get(*face_index),
1983
LightEntity::Spot { light_entity } => spot_light_entities
1984
.get(*light_entity)
1985
.expect("Failed to get spot light visible entities"),
1986
};
1987
1988
for (entity, main_entity) in visible_entities.iter().copied() {
1989
let Some((current_change_tick, pipeline_id)) =
1990
view_specialized_material_pipeline_cache.get(&main_entity)
1991
else {
1992
continue;
1993
};
1994
1995
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity)
1996
else {
1997
continue;
1998
};
1999
if !mesh_instance
2000
.flags
2001
.contains(RenderMeshInstanceFlags::SHADOW_CASTER)
2002
{
2003
continue;
2004
}
2005
2006
let mesh_layers = mesh_instance
2007
.shared
2008
.render_layers
2009
.as_ref()
2010
.unwrap_or_default();
2011
2012
let camera_layers = camera_layers.unwrap_or_default();
2013
2014
if !camera_layers.intersects(mesh_layers) {
2015
continue;
2016
}
2017
2018
// Skip the entity if it's cached in a bin and up to date.
2019
if shadow_phase.validate_cached_entity(main_entity, *current_change_tick) {
2020
continue;
2021
}
2022
2023
let Some(material_instance) = render_material_instances.instances.get(&main_entity)
2024
else {
2025
continue;
2026
};
2027
let Some(material) = render_materials.get(material_instance.asset_id) else {
2028
continue;
2029
};
2030
let Some(draw_function) =
2031
material.properties.get_draw_function(ShadowsDrawFunction)
2032
else {
2033
continue;
2034
};
2035
2036
let (vertex_slab, index_slab) =
2037
mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
2038
2039
let batch_set_key = ShadowBatchSetKey {
2040
pipeline: *pipeline_id,
2041
draw_function,
2042
material_bind_group_index: Some(material.binding.group.0),
2043
vertex_slab: vertex_slab.unwrap_or_default(),
2044
index_slab,
2045
};
2046
2047
shadow_phase.add(
2048
batch_set_key,
2049
ShadowBinKey {
2050
asset_id: mesh_instance.mesh_asset_id.into(),
2051
},
2052
(entity, main_entity),
2053
mesh_instance.current_uniform_index,
2054
BinnedRenderPhaseType::mesh(
2055
mesh_instance.should_batch(),
2056
&gpu_preprocessing_support,
2057
),
2058
*current_change_tick,
2059
);
2060
}
2061
}
2062
}
2063
}
2064
2065
pub struct Shadow {
2066
/// Determines which objects can be placed into a *batch set*.
2067
///
2068
/// Objects in a single batch set can potentially be multi-drawn together,
2069
/// if it's enabled and the current platform supports it.
2070
pub batch_set_key: ShadowBatchSetKey,
2071
/// Information that separates items into bins.
2072
pub bin_key: ShadowBinKey,
2073
pub representative_entity: (Entity, MainEntity),
2074
pub batch_range: Range<u32>,
2075
pub extra_index: PhaseItemExtraIndex,
2076
}
2077
2078
/// Information that must be identical in order to place opaque meshes in the
2079
/// same *batch set*.
2080
///
2081
/// A batch set is a set of batches that can be multi-drawn together, if
2082
/// multi-draw is in use.
2083
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2084
pub struct ShadowBatchSetKey {
2085
/// The identifier of the render pipeline.
2086
pub pipeline: CachedRenderPipelineId,
2087
2088
/// The function used to draw.
2089
pub draw_function: DrawFunctionId,
2090
2091
/// The ID of a bind group specific to the material.
2092
///
2093
/// In the case of PBR, this is the `MaterialBindGroupIndex`.
2094
pub material_bind_group_index: Option<u32>,
2095
2096
/// The ID of the slab of GPU memory that contains vertex data.
2097
///
2098
/// For non-mesh items, you can fill this with 0 if your items can be
2099
/// multi-drawn, or with a unique value if they can't.
2100
pub vertex_slab: SlabId,
2101
2102
/// The ID of the slab of GPU memory that contains index data, if present.
2103
///
2104
/// For non-mesh items, you can safely fill this with `None`.
2105
pub index_slab: Option<SlabId>,
2106
}
2107
2108
impl PhaseItemBatchSetKey for ShadowBatchSetKey {
2109
fn indexed(&self) -> bool {
2110
self.index_slab.is_some()
2111
}
2112
}
2113
2114
/// Data used to bin each object in the shadow map phase.
2115
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2116
pub struct ShadowBinKey {
2117
/// The object.
2118
pub asset_id: UntypedAssetId,
2119
}
2120
2121
impl PhaseItem for Shadow {
2122
#[inline]
2123
fn entity(&self) -> Entity {
2124
self.representative_entity.0
2125
}
2126
2127
fn main_entity(&self) -> MainEntity {
2128
self.representative_entity.1
2129
}
2130
2131
#[inline]
2132
fn draw_function(&self) -> DrawFunctionId {
2133
self.batch_set_key.draw_function
2134
}
2135
2136
#[inline]
2137
fn batch_range(&self) -> &Range<u32> {
2138
&self.batch_range
2139
}
2140
2141
#[inline]
2142
fn batch_range_mut(&mut self) -> &mut Range<u32> {
2143
&mut self.batch_range
2144
}
2145
2146
#[inline]
2147
fn extra_index(&self) -> PhaseItemExtraIndex {
2148
self.extra_index.clone()
2149
}
2150
2151
#[inline]
2152
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
2153
(&mut self.batch_range, &mut self.extra_index)
2154
}
2155
}
2156
2157
impl BinnedPhaseItem for Shadow {
2158
type BatchSetKey = ShadowBatchSetKey;
2159
type BinKey = ShadowBinKey;
2160
2161
#[inline]
2162
fn new(
2163
batch_set_key: Self::BatchSetKey,
2164
bin_key: Self::BinKey,
2165
representative_entity: (Entity, MainEntity),
2166
batch_range: Range<u32>,
2167
extra_index: PhaseItemExtraIndex,
2168
) -> Self {
2169
Shadow {
2170
batch_set_key,
2171
bin_key,
2172
representative_entity,
2173
batch_range,
2174
extra_index,
2175
}
2176
}
2177
}
2178
2179
impl CachedRenderPipelinePhaseItem for Shadow {
2180
#[inline]
2181
fn cached_pipeline(&self) -> CachedRenderPipelineId {
2182
self.batch_set_key.pipeline
2183
}
2184
}
2185
2186
/// The rendering node that renders meshes that were "visible" (so to speak)
2187
/// from a light last frame.
2188
///
2189
/// If occlusion culling for a light is disabled, then this node simply renders
2190
/// all meshes in range of the light.
2191
#[derive(Deref, DerefMut)]
2192
pub struct EarlyShadowPassNode(ShadowPassNode);
2193
2194
/// The rendering node that renders meshes that became newly "visible" (so to
2195
/// speak) from a light this frame.
2196
///
2197
/// If occlusion culling for a light is disabled, then this node does nothing.
2198
#[derive(Deref, DerefMut)]
2199
pub struct LateShadowPassNode(ShadowPassNode);
2200
2201
/// Encapsulates rendering logic shared between the early and late shadow pass
2202
/// nodes.
2203
pub struct ShadowPassNode {
2204
/// The query that finds cameras in which shadows are visible.
2205
main_view_query: QueryState<Read<ViewLightEntities>>,
2206
/// The query that finds shadow cascades.
2207
view_light_query: QueryState<(Read<ShadowView>, Read<ExtractedView>, Has<OcclusionCulling>)>,
2208
}
2209
2210
impl FromWorld for EarlyShadowPassNode {
2211
fn from_world(world: &mut World) -> Self {
2212
Self(ShadowPassNode::from_world(world))
2213
}
2214
}
2215
2216
impl FromWorld for LateShadowPassNode {
2217
fn from_world(world: &mut World) -> Self {
2218
Self(ShadowPassNode::from_world(world))
2219
}
2220
}
2221
2222
impl FromWorld for ShadowPassNode {
2223
fn from_world(world: &mut World) -> Self {
2224
Self {
2225
main_view_query: QueryState::new(world),
2226
view_light_query: QueryState::new(world),
2227
}
2228
}
2229
}
2230
2231
impl Node for EarlyShadowPassNode {
2232
fn update(&mut self, world: &mut World) {
2233
self.0.update(world);
2234
}
2235
2236
fn run<'w>(
2237
&self,
2238
graph: &mut RenderGraphContext,
2239
render_context: &mut RenderContext<'w>,
2240
world: &'w World,
2241
) -> Result<(), NodeRunError> {
2242
self.0.run(graph, render_context, world, false)
2243
}
2244
}
2245
2246
impl Node for LateShadowPassNode {
2247
fn update(&mut self, world: &mut World) {
2248
self.0.update(world);
2249
}
2250
2251
fn run<'w>(
2252
&self,
2253
graph: &mut RenderGraphContext,
2254
render_context: &mut RenderContext<'w>,
2255
world: &'w World,
2256
) -> Result<(), NodeRunError> {
2257
self.0.run(graph, render_context, world, true)
2258
}
2259
}
2260
2261
impl ShadowPassNode {
2262
fn update(&mut self, world: &mut World) {
2263
self.main_view_query.update_archetypes(world);
2264
self.view_light_query.update_archetypes(world);
2265
}
2266
2267
/// Runs the node logic.
2268
///
2269
/// `is_late` is true if this is the late shadow pass or false if this is
2270
/// the early shadow pass.
2271
fn run<'w>(
2272
&self,
2273
graph: &mut RenderGraphContext,
2274
render_context: &mut RenderContext<'w>,
2275
world: &'w World,
2276
is_late: bool,
2277
) -> Result<(), NodeRunError> {
2278
let Some(shadow_render_phases) = world.get_resource::<ViewBinnedRenderPhases<Shadow>>()
2279
else {
2280
return Ok(());
2281
};
2282
2283
if let Ok(view_lights) = self.main_view_query.get_manual(world, graph.view_entity()) {
2284
for view_light_entity in view_lights.lights.iter().copied() {
2285
let Ok((view_light, extracted_light_view, occlusion_culling)) =
2286
self.view_light_query.get_manual(world, view_light_entity)
2287
else {
2288
continue;
2289
};
2290
2291
// There's no need for a late shadow pass if the light isn't
2292
// using occlusion culling.
2293
if is_late && !occlusion_culling {
2294
continue;
2295
}
2296
2297
let Some(shadow_phase) =
2298
shadow_render_phases.get(&extracted_light_view.retained_view_entity)
2299
else {
2300
continue;
2301
};
2302
2303
let depth_stencil_attachment =
2304
Some(view_light.depth_attachment.get_attachment(StoreOp::Store));
2305
2306
let diagnostics = render_context.diagnostic_recorder();
2307
render_context.add_command_buffer_generation_task(move |render_device| {
2308
#[cfg(feature = "trace")]
2309
let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered();
2310
let mut command_encoder =
2311
render_device.create_command_encoder(&CommandEncoderDescriptor {
2312
label: Some("shadow_pass_command_encoder"),
2313
});
2314
2315
let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
2316
label: Some(&view_light.pass_name),
2317
color_attachments: &[],
2318
depth_stencil_attachment,
2319
timestamp_writes: None,
2320
occlusion_query_set: None,
2321
});
2322
2323
let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
2324
let pass_span =
2325
diagnostics.pass_span(&mut render_pass, view_light.pass_name.clone());
2326
2327
if let Err(err) =
2328
shadow_phase.render(&mut render_pass, world, view_light_entity)
2329
{
2330
error!("Error encountered while rendering the shadow phase {err:?}");
2331
}
2332
2333
pass_span.end(&mut render_pass);
2334
drop(render_pass);
2335
command_encoder.finish()
2336
});
2337
}
2338
}
2339
2340
Ok(())
2341
}
2342
}
2343
2344
/// Creates the [`ClusterableObjectType`] data for a point or spot light.
2345
fn point_or_spot_light_to_clusterable(point_light: &ExtractedPointLight) -> ClusterableObjectType {
2346
match point_light.spot_light_angles {
2347
Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
2348
outer_angle,
2349
shadows_enabled: point_light.shadows_enabled,
2350
volumetric: point_light.volumetric,
2351
},
2352
None => ClusterableObjectType::PointLight {
2353
shadows_enabled: point_light.shadows_enabled,
2354
volumetric: point_light.volumetric,
2355
},
2356
}
2357
}
2358
2359