@@ -12,11 +12,12 @@ use bevy_render::{
12
12
view:: { ComputedVisibility , RenderLayers , Visibility , VisibleEntities } ,
13
13
} ;
14
14
use bevy_transform:: components:: GlobalTransform ;
15
+ use bevy_utils:: tracing:: warn;
15
16
use bevy_window:: Windows ;
16
17
17
18
use crate :: {
18
19
calculate_cluster_factors, CubeMapFace , CubemapVisibleEntities , ViewClusterBindings ,
19
- CUBE_MAP_FACES , POINT_LIGHT_NEAR_Z ,
20
+ CUBE_MAP_FACES , MAX_POINT_LIGHTS , POINT_LIGHT_NEAR_Z ,
20
21
} ;
21
22
22
23
/// A light that emits light in all directions from a central point.
@@ -478,14 +479,91 @@ fn ndc_position_to_cluster(
478
479
. clamp ( UVec3 :: ZERO , cluster_dimensions - UVec3 :: ONE )
479
480
}
480
481
482
+ // Sort point lights with shadows enabled first, then by a stable key so that the index
483
+ // can be used to render at most `MAX_POINT_LIGHT_SHADOW_MAPS` point light shadows, and
484
+ // we keep a stable set of lights visible
485
+ pub ( crate ) fn point_light_order (
486
+ ( entity_1, shadows_enabled_1) : ( & Entity , & bool ) ,
487
+ ( entity_2, shadows_enabled_2) : ( & Entity , & bool ) ,
488
+ ) -> std:: cmp:: Ordering {
489
+ shadows_enabled_1
490
+ . cmp ( shadows_enabled_2)
491
+ . reverse ( )
492
+ . then_with ( || entity_1. cmp ( entity_2) )
493
+ }
494
+
495
+ #[ derive( Clone , Copy ) ]
496
+ // data required for assigning lights to clusters
497
+ pub ( crate ) struct PointLightAssignmentData {
498
+ entity : Entity ,
499
+ translation : Vec3 ,
500
+ range : f32 ,
501
+ shadows_enabled : bool ,
502
+ }
503
+
481
504
// NOTE: Run this before update_point_light_frusta!
482
- pub fn assign_lights_to_clusters (
505
+ pub ( crate ) fn assign_lights_to_clusters (
483
506
mut commands : Commands ,
484
507
mut global_lights : ResMut < VisiblePointLights > ,
485
508
mut views : Query < ( Entity , & GlobalTransform , & Camera , & Frustum , & mut Clusters ) > ,
486
- lights : Query < ( Entity , & GlobalTransform , & PointLight ) > ,
509
+ lights_query : Query < ( Entity , & GlobalTransform , & PointLight ) > ,
510
+ mut lights : Local < Vec < PointLightAssignmentData > > ,
511
+ mut max_point_lights_warning_emitted : Local < bool > ,
487
512
) {
488
- let light_count = lights. iter ( ) . count ( ) ;
513
+ // collect just the relevant light query data into a persisted vec to avoid reallocating each frame
514
+ lights. extend (
515
+ lights_query
516
+ . iter ( )
517
+ . map ( |( entity, transform, light) | PointLightAssignmentData {
518
+ entity,
519
+ translation : transform. translation ,
520
+ shadows_enabled : light. shadows_enabled ,
521
+ range : light. range ,
522
+ } ) ,
523
+ ) ;
524
+
525
+ if lights. len ( ) > MAX_POINT_LIGHTS {
526
+ lights. sort_by ( |light_1, light_2| {
527
+ point_light_order (
528
+ ( & light_1. entity , & light_1. shadows_enabled ) ,
529
+ ( & light_2. entity , & light_2. shadows_enabled ) ,
530
+ )
531
+ } ) ;
532
+
533
+ // check each light against each view's frustum, keep only those that affect at least one of our views
534
+ let frusta: Vec < _ > = views. iter ( ) . map ( |( _, _, _, frustum, _) | * frustum) . collect ( ) ;
535
+ let mut lights_in_view_count = 0 ;
536
+ lights. retain ( |light| {
537
+ // take one extra light to check if we should emit the warning
538
+ if lights_in_view_count == MAX_POINT_LIGHTS + 1 {
539
+ false
540
+ } else {
541
+ let light_sphere = Sphere {
542
+ center : light. translation ,
543
+ radius : light. range ,
544
+ } ;
545
+
546
+ let light_in_view = frusta
547
+ . iter ( )
548
+ . any ( |frustum| frustum. intersects_sphere ( & light_sphere) ) ;
549
+
550
+ if light_in_view {
551
+ lights_in_view_count += 1 ;
552
+ }
553
+
554
+ light_in_view
555
+ }
556
+ } ) ;
557
+
558
+ if lights. len ( ) > MAX_POINT_LIGHTS && !* max_point_lights_warning_emitted {
559
+ warn ! ( "MAX_POINT_LIGHTS ({}) exceeded" , MAX_POINT_LIGHTS ) ;
560
+ * max_point_lights_warning_emitted = true ;
561
+ }
562
+
563
+ lights. truncate ( MAX_POINT_LIGHTS ) ;
564
+ }
565
+
566
+ let light_count = lights. len ( ) ;
489
567
let mut global_lights_set = HashSet :: with_capacity ( light_count) ;
490
568
for ( view_entity, view_transform, camera, frustum, mut clusters) in views. iter_mut ( ) {
491
569
let view_transform = view_transform. compute_matrix ( ) ;
@@ -504,9 +582,9 @@ pub fn assign_lights_to_clusters(
504
582
vec ! [ VisiblePointLights :: from_light_count( light_count) ; cluster_count] ;
505
583
let mut visible_lights = Vec :: with_capacity ( light_count) ;
506
584
507
- for ( light_entity , light_transform , light) in lights. iter ( ) {
585
+ for light in lights. iter ( ) {
508
586
let light_sphere = Sphere {
509
- center : light_transform . translation ,
587
+ center : light . translation ,
510
588
radius : light. range ,
511
589
} ;
512
590
@@ -516,8 +594,8 @@ pub fn assign_lights_to_clusters(
516
594
}
517
595
518
596
// NOTE: The light intersects the frustum so it must be visible and part of the global set
519
- global_lights_set. insert ( light_entity ) ;
520
- visible_lights. push ( light_entity ) ;
597
+ global_lights_set. insert ( light . entity ) ;
598
+ visible_lights. push ( light . entity ) ;
521
599
522
600
// Calculate an AABB for the light in view space, find the corresponding clusters for the min and max
523
601
// points of the AABB, then iterate over just those clusters for this light
@@ -599,7 +677,7 @@ pub fn assign_lights_to_clusters(
599
677
let cluster_index = ( col_offset + z) as usize ;
600
678
let cluster_aabb = & clusters. aabbs [ cluster_index] ;
601
679
if light_sphere. intersects_obb ( cluster_aabb, & view_transform) {
602
- clusters_lights[ cluster_index] . entities . push ( light_entity ) ;
680
+ clusters_lights[ cluster_index] . entities . push ( light . entity ) ;
603
681
}
604
682
}
605
683
}
@@ -617,6 +695,7 @@ pub fn assign_lights_to_clusters(
617
695
} ) ;
618
696
}
619
697
global_lights. entities = global_lights_set. into_iter ( ) . collect ( ) ;
698
+ lights. clear ( ) ;
620
699
}
621
700
622
701
pub fn update_directional_light_frusta (
0 commit comments