1use bevy_camera::{
4 primitives::{Aabb, Frustum, HalfSpace, Sphere},
5 visibility::{RenderLayers, ViewVisibility},
6 Camera,
7};
8use bevy_ecs::{
9 entity::Entity,
10 query::{Has, With},
11 system::{Commands, Local, Query, Res, ResMut},
12};
13use bevy_math::{
14 ops::{self, sin_cos},
15 Mat4, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles as _, Vec4, Vec4Swizzles as _,
16};
17use bevy_transform::components::GlobalTransform;
18use bevy_utils::prelude::default;
19use tracing::warn;
20
21use super::{
22 ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings,
23 GlobalVisibleClusterableObjects, VisibleClusterableObjects,
24};
25use crate::{EnvironmentMapLight, LightProbe, PointLight, SpotLight, VolumetricLight};
26
27const NDC_MIN: Vec2 = Vec2::NEG_ONE;
28const NDC_MAX: Vec2 = Vec2::ONE;
29
30const VEC2_HALF: Vec2 = Vec2::splat(0.5);
31const VEC2_HALF_NEGATIVE_Y: Vec2 = Vec2::new(0.5, -0.5);
32
33#[derive(Clone, Debug)]
35pub(crate) struct ClusterableObjectAssignmentData {
36 entity: Entity,
37 transform: GlobalTransform,
40 range: f32,
41 object_type: ClusterableObjectType,
42 render_layers: RenderLayers,
43}
44
45impl ClusterableObjectAssignmentData {
46 pub fn sphere(&self) -> Sphere {
47 Sphere {
48 center: self.transform.translation_vec3a(),
49 radius: self.range,
50 }
51 }
52}
53
54#[derive(Clone, Copy, Debug)]
57pub enum ClusterableObjectType {
58 PointLight {
60 shadows_enabled: bool,
64
65 volumetric: bool,
69 },
70
71 SpotLight {
73 shadows_enabled: bool,
77
78 volumetric: bool,
82
83 outer_angle: f32,
85 },
86
87 ReflectionProbe,
89
90 IrradianceVolume,
92
93 Decal,
95}
96
97impl ClusterableObjectType {
98 pub fn ordering(&self) -> (u8, bool, bool) {
106 match *self {
107 ClusterableObjectType::PointLight {
108 shadows_enabled,
109 volumetric,
110 } => (0, !shadows_enabled, !volumetric),
111 ClusterableObjectType::SpotLight {
112 shadows_enabled,
113 volumetric,
114 ..
115 } => (1, !shadows_enabled, !volumetric),
116 ClusterableObjectType::ReflectionProbe => (2, false, false),
117 ClusterableObjectType::IrradianceVolume => (3, false, false),
118 ClusterableObjectType::Decal => (4, false, false),
119 }
120 }
121}
122
123pub(crate) fn assign_objects_to_clusters(
125 mut commands: Commands,
126 mut global_clusterable_objects: ResMut<GlobalVisibleClusterableObjects>,
127 mut views: Query<(
128 Entity,
129 &GlobalTransform,
130 &Camera,
131 &Frustum,
132 &ClusterConfig,
133 &mut Clusters,
134 Option<&RenderLayers>,
135 Option<&mut VisibleClusterableObjects>,
136 )>,
137 point_lights_query: Query<(
138 Entity,
139 &GlobalTransform,
140 &PointLight,
141 Option<&RenderLayers>,
142 Option<&VolumetricLight>,
143 &ViewVisibility,
144 )>,
145 spot_lights_query: Query<(
146 Entity,
147 &GlobalTransform,
148 &SpotLight,
149 Option<&RenderLayers>,
150 Option<&VolumetricLight>,
151 &ViewVisibility,
152 )>,
153 light_probes_query: Query<
154 (Entity, &GlobalTransform, Has<EnvironmentMapLight>),
155 With<LightProbe>,
156 >,
157 decals_query: Query<(Entity, &GlobalTransform), With<ClusteredDecal>>,
158 mut clusterable_objects: Local<Vec<ClusterableObjectAssignmentData>>,
159 mut cluster_aabb_spheres: Local<Vec<Option<Sphere>>>,
160 mut max_clusterable_objects_warning_emitted: Local<bool>,
161 global_cluster_settings: Option<Res<GlobalClusterSettings>>,
162) {
163 let Some(global_cluster_settings) = global_cluster_settings else {
164 return;
165 };
166
167 global_clusterable_objects.entities.clear();
168 clusterable_objects.clear();
169 clusterable_objects.extend(
171 point_lights_query
172 .iter()
173 .filter(|(.., visibility)| visibility.get())
174 .map(
175 |(entity, transform, point_light, maybe_layers, volumetric, _visibility)| {
176 ClusterableObjectAssignmentData {
177 entity,
178 transform: GlobalTransform::from_translation(transform.translation()),
179 range: point_light.range,
180 object_type: ClusterableObjectType::PointLight {
181 shadows_enabled: point_light.shadows_enabled,
182 volumetric: volumetric.is_some(),
183 },
184 render_layers: maybe_layers.unwrap_or_default().clone(),
185 }
186 },
187 ),
188 );
189 clusterable_objects.extend(
190 spot_lights_query
191 .iter()
192 .filter(|(.., visibility)| visibility.get())
193 .map(
194 |(entity, transform, spot_light, maybe_layers, volumetric, _visibility)| {
195 ClusterableObjectAssignmentData {
196 entity,
197 transform: *transform,
198 range: spot_light.range,
199 object_type: ClusterableObjectType::SpotLight {
200 outer_angle: spot_light.outer_angle,
201 shadows_enabled: spot_light.shadows_enabled,
202 volumetric: volumetric.is_some(),
203 },
204 render_layers: maybe_layers.unwrap_or_default().clone(),
205 }
206 },
207 ),
208 );
209
210 if global_cluster_settings.supports_storage_buffers {
217 clusterable_objects.extend(light_probes_query.iter().map(
218 |(entity, transform, is_reflection_probe)| ClusterableObjectAssignmentData {
219 entity,
220 transform: *transform,
221 range: transform.radius_vec3a(Vec3A::ONE),
222 object_type: if is_reflection_probe {
223 ClusterableObjectType::ReflectionProbe
224 } else {
225 ClusterableObjectType::IrradianceVolume
226 },
227 render_layers: RenderLayers::default(),
228 },
229 ));
230 }
231
232 if global_cluster_settings.clustered_decals_are_usable {
234 clusterable_objects.extend(decals_query.iter().map(|(entity, transform)| {
235 ClusterableObjectAssignmentData {
236 entity,
237 transform: *transform,
238 range: transform.scale().length(),
239 object_type: ClusterableObjectType::Decal,
240 render_layers: RenderLayers::default(),
241 }
242 }));
243 }
244
245 if clusterable_objects.len() > global_cluster_settings.max_uniform_buffer_clusterable_objects
246 && !global_cluster_settings.supports_storage_buffers
247 {
248 clusterable_objects.sort_by_cached_key(|clusterable_object| {
249 (
250 clusterable_object.object_type.ordering(),
251 clusterable_object.entity,
252 )
253 });
254
255 let frusta: Vec<_> = views
258 .iter()
259 .map(|(_, _, _, frustum, _, _, _, _)| *frustum)
260 .collect();
261 let mut clusterable_objects_in_view_count = 0;
262 clusterable_objects.retain(|clusterable_object| {
263 if clusterable_objects_in_view_count
265 == global_cluster_settings.max_uniform_buffer_clusterable_objects + 1
266 {
267 false
268 } else {
269 let clusterable_object_sphere = clusterable_object.sphere();
270 let clusterable_object_in_view = frusta
271 .iter()
272 .any(|frustum| frustum.intersects_sphere(&clusterable_object_sphere, true));
273
274 if clusterable_object_in_view {
275 clusterable_objects_in_view_count += 1;
276 }
277
278 clusterable_object_in_view
279 }
280 });
281
282 if clusterable_objects.len()
283 > global_cluster_settings.max_uniform_buffer_clusterable_objects
284 && !*max_clusterable_objects_warning_emitted
285 {
286 warn!(
287 "max_uniform_buffer_clusterable_objects ({}) exceeded",
288 global_cluster_settings.max_uniform_buffer_clusterable_objects
289 );
290 *max_clusterable_objects_warning_emitted = true;
291 }
292
293 clusterable_objects
294 .truncate(global_cluster_settings.max_uniform_buffer_clusterable_objects);
295 }
296
297 for (
298 view_entity,
299 camera_transform,
300 camera,
301 frustum,
302 config,
303 clusters,
304 maybe_layers,
305 mut visible_clusterable_objects,
306 ) in &mut views
307 {
308 let view_layers = maybe_layers.unwrap_or_default();
309 let clusters = clusters.into_inner();
310
311 if matches!(config, ClusterConfig::None) {
312 if visible_clusterable_objects.is_some() {
313 commands
314 .entity(view_entity)
315 .remove::<VisibleClusterableObjects>();
316 }
317 clusters.clear();
318 continue;
319 }
320
321 let screen_size = match camera.physical_viewport_size() {
322 Some(screen_size) if screen_size.x != 0 && screen_size.y != 0 => screen_size,
323 _ => {
324 clusters.clear();
325 continue;
326 }
327 };
328
329 let mut requested_cluster_dimensions = config.dimensions_for_screen_size(screen_size);
330
331 let world_from_view = camera_transform.affine();
332 let view_from_world_scale = camera_transform.compute_transform().scale.recip();
333 let view_from_world_scale_max = view_from_world_scale.abs().max_element();
334 let view_from_world = Mat4::from(world_from_view.inverse());
335 let is_orthographic = camera.clip_from_view().w_axis.w == 1.0;
336
337 let far_z = match config.far_z_mode() {
338 ClusterFarZMode::MaxClusterableObjectRange => {
339 let view_from_world_row_2 = view_from_world.row(2);
340 clusterable_objects
341 .iter()
342 .map(|object| {
343 -view_from_world_row_2.dot(object.transform.translation().extend(1.0))
344 + object.range * view_from_world_scale.z
345 })
346 .reduce(f32::max)
347 .unwrap_or(0.0)
348 }
349 ClusterFarZMode::Constant(far) => far,
350 };
351 let first_slice_depth = match (is_orthographic, requested_cluster_dimensions.z) {
352 (true, _) => {
353 (camera.clip_from_view().w_axis.z - 1.0) / camera.clip_from_view().z_axis.z
362 }
363 (false, 1) => config.first_slice_depth().max(far_z),
364 _ => config.first_slice_depth(),
365 };
366 let first_slice_depth = first_slice_depth * view_from_world_scale.z;
367
368 let far_z = far_z.max(first_slice_depth);
370 let cluster_factors = calculate_cluster_factors(
371 first_slice_depth,
372 far_z,
373 requested_cluster_dimensions.z as f32,
374 is_orthographic,
375 );
376
377 if config.dynamic_resizing() {
378 let mut cluster_index_estimate = 0.0;
379 for clusterable_object in &clusterable_objects {
380 let clusterable_object_sphere = clusterable_object.sphere();
381
382 if !frustum.intersects_sphere(&clusterable_object_sphere, true) {
384 continue;
385 }
386
387 let (clusterable_object_aabb_min, clusterable_object_aabb_max) =
391 cluster_space_clusterable_object_aabb(
392 view_from_world,
393 view_from_world_scale,
394 camera.clip_from_view(),
395 &clusterable_object_sphere,
396 );
397
398 let z_cluster_min = view_z_to_z_slice(
400 cluster_factors,
401 requested_cluster_dimensions.z,
402 clusterable_object_aabb_min.z,
403 is_orthographic,
404 );
405 let z_cluster_max = view_z_to_z_slice(
406 cluster_factors,
407 requested_cluster_dimensions.z,
408 clusterable_object_aabb_max.z,
409 is_orthographic,
410 );
411 let z_count =
412 z_cluster_min.max(z_cluster_max) - z_cluster_min.min(z_cluster_max) + 1;
413
414 let xy_min = clusterable_object_aabb_min.xy();
416 let xy_max = clusterable_object_aabb_max.xy();
417 let xy_count = (xy_max - xy_min)
419 * 0.5
420 * Vec2::new(
421 requested_cluster_dimensions.x as f32,
422 requested_cluster_dimensions.y as f32,
423 );
424
425 let x_overlap = if xy_min.x <= -1.0 { 0.0 } else { 1.0 }
427 + if xy_max.x >= 1.0 { 0.0 } else { 1.0 };
428 let y_overlap = if xy_min.y <= -1.0 { 0.0 } else { 1.0 }
429 + if xy_max.y >= 1.0 { 0.0 } else { 1.0 };
430 cluster_index_estimate +=
431 (xy_count.x + x_overlap) * (xy_count.y + y_overlap) * z_count as f32;
432 }
433
434 if cluster_index_estimate
435 > global_cluster_settings.view_cluster_bindings_max_indices as f32
436 {
437 let index_ratio = global_cluster_settings.view_cluster_bindings_max_indices as f32
444 / cluster_index_estimate;
445 let xy_ratio = index_ratio.sqrt();
446
447 requested_cluster_dimensions.x =
448 ((requested_cluster_dimensions.x as f32 * xy_ratio).floor() as u32).max(1);
449 requested_cluster_dimensions.y =
450 ((requested_cluster_dimensions.y as f32 * xy_ratio).floor() as u32).max(1);
451 }
452 }
453
454 clusters.update(screen_size, requested_cluster_dimensions);
455 clusters.near = first_slice_depth;
456 clusters.far = far_z;
457
458 debug_assert!(
460 clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z <= 4096
461 );
462
463 let view_from_clip = camera.clip_from_view().inverse();
464
465 for clusterable_objects in &mut clusters.clusterable_objects {
466 clusterable_objects.entities.clear();
467 clusterable_objects.counts = default();
468 }
469 let cluster_count =
470 (clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z) as usize;
471 clusters
472 .clusterable_objects
473 .resize_with(cluster_count, VisibleClusterableObjects::default);
474
475 cluster_aabb_spheres.clear();
477 cluster_aabb_spheres.extend(core::iter::repeat_n(None, cluster_count));
478
479 let mut x_planes = Vec::with_capacity(clusters.dimensions.x as usize + 1);
481 let mut y_planes = Vec::with_capacity(clusters.dimensions.y as usize + 1);
482 let mut z_planes = Vec::with_capacity(clusters.dimensions.z as usize + 1);
483
484 if is_orthographic {
485 let x_slices = clusters.dimensions.x as f32;
486 for x in 0..=clusters.dimensions.x {
487 let x_proportion = x as f32 / x_slices;
488 let x_pos = x_proportion * 2.0 - 1.0;
489 let view_x = clip_to_view(view_from_clip, Vec4::new(x_pos, 0.0, 1.0, 1.0)).x;
490 let normal = Vec3::X;
491 let d = view_x * normal.x;
492 x_planes.push(HalfSpace::new(normal.extend(d)));
493 }
494
495 let y_slices = clusters.dimensions.y as f32;
496 for y in 0..=clusters.dimensions.y {
497 let y_proportion = 1.0 - y as f32 / y_slices;
498 let y_pos = y_proportion * 2.0 - 1.0;
499 let view_y = clip_to_view(view_from_clip, Vec4::new(0.0, y_pos, 1.0, 1.0)).y;
500 let normal = Vec3::Y;
501 let d = view_y * normal.y;
502 y_planes.push(HalfSpace::new(normal.extend(d)));
503 }
504 } else {
505 let x_slices = clusters.dimensions.x as f32;
506 for x in 0..=clusters.dimensions.x {
507 let x_proportion = x as f32 / x_slices;
508 let x_pos = x_proportion * 2.0 - 1.0;
509 let nb = clip_to_view(view_from_clip, Vec4::new(x_pos, -1.0, 1.0, 1.0)).xyz();
510 let nt = clip_to_view(view_from_clip, Vec4::new(x_pos, 1.0, 1.0, 1.0)).xyz();
511 let normal = nb.cross(nt);
512 let d = nb.dot(normal);
513 x_planes.push(HalfSpace::new(normal.extend(d)));
514 }
515
516 let y_slices = clusters.dimensions.y as f32;
517 for y in 0..=clusters.dimensions.y {
518 let y_proportion = 1.0 - y as f32 / y_slices;
519 let y_pos = y_proportion * 2.0 - 1.0;
520 let nl = clip_to_view(view_from_clip, Vec4::new(-1.0, y_pos, 1.0, 1.0)).xyz();
521 let nr = clip_to_view(view_from_clip, Vec4::new(1.0, y_pos, 1.0, 1.0)).xyz();
522 let normal = nr.cross(nl);
523 let d = nr.dot(normal);
524 y_planes.push(HalfSpace::new(normal.extend(d)));
525 }
526 }
527
528 let z_slices = clusters.dimensions.z;
529 for z in 0..=z_slices {
530 let view_z = z_slice_to_view_z(first_slice_depth, far_z, z_slices, z, is_orthographic);
531 let normal = -Vec3::Z;
532 let d = view_z * normal.z;
533 z_planes.push(HalfSpace::new(normal.extend(d)));
534 }
535
536 let mut update_from_object_intersections = |visible_clusterable_objects: &mut Vec<
537 Entity,
538 >| {
539 for clusterable_object in &clusterable_objects {
540 if !view_layers.intersects(&clusterable_object.render_layers) {
542 continue;
543 }
544
545 let clusterable_object_sphere = clusterable_object.sphere();
546
547 if !frustum.intersects_sphere(&clusterable_object_sphere, true) {
549 continue;
550 }
551
552 global_clusterable_objects
555 .entities
556 .insert(clusterable_object.entity);
557 visible_clusterable_objects.push(clusterable_object.entity);
558
559 let (
561 clusterable_object_aabb_xy_ndc_z_view_min,
562 clusterable_object_aabb_xy_ndc_z_view_max,
563 ) = cluster_space_clusterable_object_aabb(
564 view_from_world,
565 view_from_world_scale,
566 camera.clip_from_view(),
567 &clusterable_object_sphere,
568 );
569
570 let min_cluster = ndc_position_to_cluster(
571 clusters.dimensions,
572 cluster_factors,
573 is_orthographic,
574 clusterable_object_aabb_xy_ndc_z_view_min,
575 clusterable_object_aabb_xy_ndc_z_view_min.z,
576 );
577 let max_cluster = ndc_position_to_cluster(
578 clusters.dimensions,
579 cluster_factors,
580 is_orthographic,
581 clusterable_object_aabb_xy_ndc_z_view_max,
582 clusterable_object_aabb_xy_ndc_z_view_max.z,
583 );
584 let (min_cluster, max_cluster) =
585 (min_cluster.min(max_cluster), min_cluster.max(max_cluster));
586
587 let view_clusterable_object_sphere = Sphere {
595 center: Vec3A::from_vec4(
596 view_from_world * clusterable_object_sphere.center.extend(1.0),
597 ),
598 radius: clusterable_object_sphere.radius * view_from_world_scale_max,
599 };
600 let spot_light_dir_sin_cos = match clusterable_object.object_type {
601 ClusterableObjectType::SpotLight { outer_angle, .. } => {
602 let (angle_sin, angle_cos) = sin_cos(outer_angle);
603 Some((
604 (view_from_world * clusterable_object.transform.back().extend(0.0))
605 .truncate()
606 .normalize(),
607 angle_sin,
608 angle_cos,
609 ))
610 }
611 ClusterableObjectType::Decal => {
612 None
614 }
615 ClusterableObjectType::PointLight { .. }
616 | ClusterableObjectType::ReflectionProbe
617 | ClusterableObjectType::IrradianceVolume => None,
618 };
619 let clusterable_object_center_clip =
620 camera.clip_from_view() * view_clusterable_object_sphere.center.extend(1.0);
621 let object_center_ndc =
622 clusterable_object_center_clip.xyz() / clusterable_object_center_clip.w;
623 let cluster_coordinates = ndc_position_to_cluster(
624 clusters.dimensions,
625 cluster_factors,
626 is_orthographic,
627 object_center_ndc,
628 view_clusterable_object_sphere.center.z,
629 );
630 let z_center = if object_center_ndc.z <= 1.0 {
631 Some(cluster_coordinates.z)
632 } else {
633 None
634 };
635 let y_center = if object_center_ndc.y > 1.0 {
636 None
637 } else if object_center_ndc.y < -1.0 {
638 Some(clusters.dimensions.y + 1)
639 } else {
640 Some(cluster_coordinates.y)
641 };
642 for z in min_cluster.z..=max_cluster.z {
643 let mut z_object = view_clusterable_object_sphere.clone();
644 if z_center.is_none() || z != z_center.unwrap() {
645 let z_plane = if z_center.is_some() && z < z_center.unwrap() {
649 z_planes[(z + 1) as usize]
650 } else {
651 z_planes[z as usize]
652 };
653 if let Some(projected) = project_to_plane_z(z_object, z_plane) {
656 z_object = projected;
657 } else {
658 continue;
659 }
660 }
661 for y in min_cluster.y..=max_cluster.y {
662 let mut y_object = z_object.clone();
663 if y_center.is_none() || y != y_center.unwrap() {
664 let y_plane = if y_center.is_some() && y < y_center.unwrap() {
668 y_planes[(y + 1) as usize]
669 } else {
670 y_planes[y as usize]
671 };
672 if let Some(projected) =
675 project_to_plane_y(y_object, y_plane, is_orthographic)
676 {
677 y_object = projected;
678 } else {
679 continue;
680 }
681 }
682 let mut min_x = min_cluster.x;
684 loop {
685 if min_x >= max_cluster.x
686 || -get_distance_x(
687 x_planes[(min_x + 1) as usize],
688 y_object.center,
689 is_orthographic,
690 ) + y_object.radius
691 > 0.0
692 {
693 break;
694 }
695 min_x += 1;
696 }
697 let mut max_x = max_cluster.x;
699 loop {
700 if max_x <= min_x
701 || get_distance_x(
702 x_planes[max_x as usize],
703 y_object.center,
704 is_orthographic,
705 ) + y_object.radius
706 > 0.0
707 {
708 break;
709 }
710 max_x -= 1;
711 }
712 let mut cluster_index = ((y * clusters.dimensions.x + min_x)
713 * clusters.dimensions.z
714 + z) as usize;
715
716 match clusterable_object.object_type {
717 ClusterableObjectType::SpotLight { .. } => {
718 let (view_light_direction, angle_sin, angle_cos) =
719 spot_light_dir_sin_cos.unwrap();
720 for x in min_x..=max_x {
721 let cluster_aabb_sphere =
724 &mut cluster_aabb_spheres[cluster_index];
725 let cluster_aabb_sphere =
726 if let Some(sphere) = cluster_aabb_sphere {
727 &*sphere
728 } else {
729 let aabb = compute_aabb_for_cluster(
730 first_slice_depth,
731 far_z,
732 clusters.tile_size.as_vec2(),
733 screen_size.as_vec2(),
734 view_from_clip,
735 is_orthographic,
736 clusters.dimensions,
737 UVec3::new(x, y, z),
738 );
739 let sphere = Sphere {
740 center: aabb.center,
741 radius: aabb.half_extents.length(),
742 };
743 *cluster_aabb_sphere = Some(sphere);
744 cluster_aabb_sphere.as_ref().unwrap()
745 };
746
747 let spot_light_offset = Vec3::from(
749 view_clusterable_object_sphere.center
750 - cluster_aabb_sphere.center,
751 );
752 let spot_light_dist_sq = spot_light_offset.length_squared();
753 let v1_len = spot_light_offset.dot(view_light_direction);
754
755 let distance_closest_point = (angle_cos
756 * (spot_light_dist_sq - v1_len * v1_len).sqrt())
757 - v1_len * angle_sin;
758 let angle_cull =
759 distance_closest_point > cluster_aabb_sphere.radius;
760
761 let front_cull = v1_len
762 > cluster_aabb_sphere.radius
763 + clusterable_object.range * view_from_world_scale_max;
764 let back_cull = v1_len < -cluster_aabb_sphere.radius;
765
766 if !angle_cull && !front_cull && !back_cull {
767 clusters.clusterable_objects[cluster_index]
769 .entities
770 .push(clusterable_object.entity);
771 clusters.clusterable_objects[cluster_index]
772 .counts
773 .spot_lights += 1;
774 }
775 cluster_index += clusters.dimensions.z as usize;
776 }
777 }
778
779 ClusterableObjectType::PointLight { .. } => {
780 for _ in min_x..=max_x {
781 clusters.clusterable_objects[cluster_index]
783 .entities
784 .push(clusterable_object.entity);
785 clusters.clusterable_objects[cluster_index]
786 .counts
787 .point_lights += 1;
788 cluster_index += clusters.dimensions.z as usize;
789 }
790 }
791
792 ClusterableObjectType::ReflectionProbe => {
793 for _ in min_x..=max_x {
799 clusters.clusterable_objects[cluster_index]
800 .entities
801 .push(clusterable_object.entity);
802 clusters.clusterable_objects[cluster_index]
803 .counts
804 .reflection_probes += 1;
805 cluster_index += clusters.dimensions.z as usize;
806 }
807 }
808
809 ClusterableObjectType::IrradianceVolume => {
810 for _ in min_x..=max_x {
816 clusters.clusterable_objects[cluster_index]
817 .entities
818 .push(clusterable_object.entity);
819 clusters.clusterable_objects[cluster_index]
820 .counts
821 .irradiance_volumes += 1;
822 cluster_index += clusters.dimensions.z as usize;
823 }
824 }
825
826 ClusterableObjectType::Decal => {
827 for _ in min_x..=max_x {
833 clusters.clusterable_objects[cluster_index]
834 .entities
835 .push(clusterable_object.entity);
836 clusters.clusterable_objects[cluster_index].counts.decals += 1;
837 cluster_index += clusters.dimensions.z as usize;
838 }
839 }
840 }
841 }
842 }
843 }
844 };
845
846 if let Some(visible_clusterable_objects) = visible_clusterable_objects.as_mut() {
848 visible_clusterable_objects.entities.clear();
849 update_from_object_intersections(&mut visible_clusterable_objects.entities);
850 } else {
851 let mut entities = Vec::new();
852 update_from_object_intersections(&mut entities);
853 commands
854 .entity(view_entity)
855 .insert(VisibleClusterableObjects {
856 entities,
857 ..Default::default()
858 });
859 }
860 }
861}
862
863pub fn calculate_cluster_factors(
864 near: f32,
865 far: f32,
866 z_slices: f32,
867 is_orthographic: bool,
868) -> Vec2 {
869 if is_orthographic {
870 Vec2::new(-near, z_slices / (-far - -near))
871 } else {
872 let z_slices_of_ln_zfar_over_znear = (z_slices - 1.0) / ops::ln(far / near);
873 Vec2::new(
874 z_slices_of_ln_zfar_over_znear,
875 ops::ln(near) * z_slices_of_ln_zfar_over_znear,
876 )
877 }
878}
879
880fn compute_aabb_for_cluster(
881 z_near: f32,
882 z_far: f32,
883 tile_size: Vec2,
884 screen_size: Vec2,
885 view_from_clip: Mat4,
886 is_orthographic: bool,
887 cluster_dimensions: UVec3,
888 ijk: UVec3,
889) -> Aabb {
890 let ijk = ijk.as_vec3();
891
892 let p_min = ijk.xy() * tile_size;
894 let p_max = p_min + tile_size;
895
896 let cluster_min;
897 let cluster_max;
898 if is_orthographic {
899 let mut p_min = screen_to_view(screen_size, view_from_clip, p_min, 0.0).xyz();
904 let mut p_max = screen_to_view(screen_size, view_from_clip, p_max, 0.0).xyz();
905
906 p_min.z = -z_near + (z_near - z_far) * ijk.z / cluster_dimensions.z as f32;
908 p_max.z = -z_near + (z_near - z_far) * (ijk.z + 1.0) / cluster_dimensions.z as f32;
909
910 cluster_min = p_min.min(p_max);
911 cluster_max = p_min.max(p_max);
912 } else {
913 let p_min = screen_to_view(screen_size, view_from_clip, p_min, 1.0);
916 let p_max = screen_to_view(screen_size, view_from_clip, p_max, 1.0);
917
918 let z_far_over_z_near = -z_far / -z_near;
919 let cluster_near = if ijk.z == 0.0 {
920 0.0
921 } else {
922 -z_near
923 * ops::powf(
924 z_far_over_z_near,
925 (ijk.z - 1.0) / (cluster_dimensions.z - 1) as f32,
926 )
927 };
928 let cluster_far = if cluster_dimensions.z == 1 {
931 -z_far
932 } else {
933 -z_near * ops::powf(z_far_over_z_near, ijk.z / (cluster_dimensions.z - 1) as f32)
934 };
935
936 let p_min_near = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_near);
938 let p_min_far = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_far);
939 let p_max_near = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_near);
940 let p_max_far = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_far);
941
942 cluster_min = p_min_near.min(p_min_far).min(p_max_near.min(p_max_far));
943 cluster_max = p_min_near.max(p_min_far).max(p_max_near.max(p_max_far));
944 }
945
946 Aabb::from_min_max(cluster_min, cluster_max)
947}
948
949fn z_slice_to_view_z(
951 near: f32,
952 far: f32,
953 z_slices: u32,
954 z_slice: u32,
955 is_orthographic: bool,
956) -> f32 {
957 if is_orthographic {
958 return -near - (far - near) * z_slice as f32 / z_slices as f32;
959 }
960
961 if z_slice == 0 {
963 0.0
964 } else {
965 -near * ops::powf(far / near, (z_slice - 1) as f32 / (z_slices - 1) as f32)
966 }
967}
968
969fn ndc_position_to_cluster(
970 cluster_dimensions: UVec3,
971 cluster_factors: Vec2,
972 is_orthographic: bool,
973 ndc_p: Vec3,
974 view_z: f32,
975) -> UVec3 {
976 let cluster_dimensions_f32 = cluster_dimensions.as_vec3();
977 let frag_coord = (ndc_p.xy() * VEC2_HALF_NEGATIVE_Y + VEC2_HALF).clamp(Vec2::ZERO, Vec2::ONE);
978 let xy = (frag_coord * cluster_dimensions_f32.xy()).floor();
979 let z_slice = view_z_to_z_slice(
980 cluster_factors,
981 cluster_dimensions.z,
982 view_z,
983 is_orthographic,
984 );
985 xy.as_uvec2()
986 .extend(z_slice)
987 .clamp(UVec3::ZERO, cluster_dimensions - UVec3::ONE)
988}
989
990fn cluster_space_clusterable_object_aabb(
996 view_from_world: Mat4,
997 view_from_world_scale: Vec3,
998 clip_from_view: Mat4,
999 clusterable_object_sphere: &Sphere,
1000) -> (Vec3, Vec3) {
1001 let clusterable_object_aabb_view = Aabb {
1002 center: Vec3A::from_vec4(view_from_world * clusterable_object_sphere.center.extend(1.0)),
1003 half_extents: Vec3A::from(clusterable_object_sphere.radius * view_from_world_scale.abs()),
1004 };
1005 let (mut clusterable_object_aabb_view_min, mut clusterable_object_aabb_view_max) = (
1006 clusterable_object_aabb_view.min(),
1007 clusterable_object_aabb_view.max(),
1008 );
1009
1010 clusterable_object_aabb_view_min.z = clusterable_object_aabb_view_min.z.min(-f32::MIN_POSITIVE);
1018 clusterable_object_aabb_view_max.z = clusterable_object_aabb_view_max.z.min(-f32::MIN_POSITIVE);
1019
1020 let (
1025 clusterable_object_aabb_view_xymin_near,
1026 clusterable_object_aabb_view_xymin_far,
1027 clusterable_object_aabb_view_xymax_near,
1028 clusterable_object_aabb_view_xymax_far,
1029 ) = (
1030 clusterable_object_aabb_view_min,
1031 clusterable_object_aabb_view_min
1032 .xy()
1033 .extend(clusterable_object_aabb_view_max.z),
1034 clusterable_object_aabb_view_max
1035 .xy()
1036 .extend(clusterable_object_aabb_view_min.z),
1037 clusterable_object_aabb_view_max,
1038 );
1039 let (
1040 clusterable_object_aabb_clip_xymin_near,
1041 clusterable_object_aabb_clip_xymin_far,
1042 clusterable_object_aabb_clip_xymax_near,
1043 clusterable_object_aabb_clip_xymax_far,
1044 ) = (
1045 clip_from_view * clusterable_object_aabb_view_xymin_near.extend(1.0),
1046 clip_from_view * clusterable_object_aabb_view_xymin_far.extend(1.0),
1047 clip_from_view * clusterable_object_aabb_view_xymax_near.extend(1.0),
1048 clip_from_view * clusterable_object_aabb_view_xymax_far.extend(1.0),
1049 );
1050 let (
1051 clusterable_object_aabb_ndc_xymin_near,
1052 clusterable_object_aabb_ndc_xymin_far,
1053 clusterable_object_aabb_ndc_xymax_near,
1054 clusterable_object_aabb_ndc_xymax_far,
1055 ) = (
1056 clusterable_object_aabb_clip_xymin_near.xyz() / clusterable_object_aabb_clip_xymin_near.w,
1057 clusterable_object_aabb_clip_xymin_far.xyz() / clusterable_object_aabb_clip_xymin_far.w,
1058 clusterable_object_aabb_clip_xymax_near.xyz() / clusterable_object_aabb_clip_xymax_near.w,
1059 clusterable_object_aabb_clip_xymax_far.xyz() / clusterable_object_aabb_clip_xymax_far.w,
1060 );
1061 let (clusterable_object_aabb_ndc_min, clusterable_object_aabb_ndc_max) = (
1062 clusterable_object_aabb_ndc_xymin_near
1063 .min(clusterable_object_aabb_ndc_xymin_far)
1064 .min(clusterable_object_aabb_ndc_xymax_near)
1065 .min(clusterable_object_aabb_ndc_xymax_far),
1066 clusterable_object_aabb_ndc_xymin_near
1067 .max(clusterable_object_aabb_ndc_xymin_far)
1068 .max(clusterable_object_aabb_ndc_xymax_near)
1069 .max(clusterable_object_aabb_ndc_xymax_far),
1070 );
1071
1072 let (aabb_min_ndc, aabb_max_ndc) = (
1074 clusterable_object_aabb_ndc_min.xy().clamp(NDC_MIN, NDC_MAX),
1075 clusterable_object_aabb_ndc_max.xy().clamp(NDC_MIN, NDC_MAX),
1076 );
1077
1078 (
1080 aabb_min_ndc.extend(clusterable_object_aabb_view_min.z),
1081 aabb_max_ndc.extend(clusterable_object_aabb_view_max.z),
1082 )
1083}
1084
1085fn line_intersection_to_z_plane(origin: Vec3, p: Vec3, z: f32) -> Vec3 {
1087 let v = p - origin;
1088 let t = (z - Vec3::Z.dot(origin)) / Vec3::Z.dot(v);
1089 origin + t * v
1090}
1091
1092fn view_z_to_z_slice(
1094 cluster_factors: Vec2,
1095 z_slices: u32,
1096 view_z: f32,
1097 is_orthographic: bool,
1098) -> u32 {
1099 let z_slice = if is_orthographic {
1100 ((view_z - cluster_factors.x) * cluster_factors.y).floor() as u32
1102 } else {
1103 (ops::ln(-view_z) * cluster_factors.x - cluster_factors.y + 1.0) as u32
1105 };
1106 z_slice.min(z_slices - 1)
1109}
1110
1111fn clip_to_view(view_from_clip: Mat4, clip: Vec4) -> Vec4 {
1112 let view = view_from_clip * clip;
1113 view / view.w
1114}
1115
1116fn screen_to_view(screen_size: Vec2, view_from_clip: Mat4, screen: Vec2, ndc_z: f32) -> Vec4 {
1117 let tex_coord = screen / screen_size;
1118 let clip = Vec4::new(
1119 tex_coord.x * 2.0 - 1.0,
1120 (1.0 - tex_coord.y) * 2.0 - 1.0,
1121 ndc_z,
1122 1.0,
1123 );
1124 clip_to_view(view_from_clip, clip)
1125}
1126
1127fn get_distance_x(plane: HalfSpace, point: Vec3A, is_orthographic: bool) -> f32 {
1129 if is_orthographic {
1130 point.x - plane.d()
1131 } else {
1132 plane.normal_d().xz().dot(point.xz())
1137 }
1138}
1139
1140fn project_to_plane_z(z_object: Sphere, z_plane: HalfSpace) -> Option<Sphere> {
1142 let z = z_plane.d() / z_plane.normal_d().z;
1150 let distance_to_plane = z - z_object.center.z;
1151 if distance_to_plane.abs() > z_object.radius {
1152 return None;
1153 }
1154 Some(Sphere {
1155 center: Vec3A::from(z_object.center.xy().extend(z)),
1156 radius: (z_object.radius * z_object.radius - distance_to_plane * distance_to_plane).sqrt(),
1159 })
1160}
1161
1162fn project_to_plane_y(
1164 y_object: Sphere,
1165 y_plane: HalfSpace,
1166 is_orthographic: bool,
1167) -> Option<Sphere> {
1168 let distance_to_plane = if is_orthographic {
1169 y_plane.d() - y_object.center.y
1170 } else {
1171 -y_object.center.yz().dot(y_plane.normal_d().yz())
1172 };
1173
1174 if distance_to_plane.abs() > y_object.radius {
1175 return None;
1176 }
1177 Some(Sphere {
1178 center: y_object.center + distance_to_plane * y_plane.normal(),
1179 radius: (y_object.radius * y_object.radius - distance_to_plane * distance_to_plane).sqrt(),
1180 })
1181}