1use bevy_camera::{
4 primitives::{Aabb, Frustum, Sphere},
5 visibility::{RenderLayers, ViewVisibility},
6 Camera,
7};
8use bevy_ecs::{
9 entity::Entity,
10 query::{Has, With},
11 system::{Local, Query, Res},
12};
13use bevy_math::{
14 ops::{self, sin_cos},
15 Mat4, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles as _, Vec4, Vec4Swizzles as _,
16};
17use bevy_shape::HalfSpace;
18use bevy_transform::components::GlobalTransform;
19use tracing::{error, warn};
20
21use super::{ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings};
22use crate::{
23 cluster::ClusterableObjects, EnvironmentMapLight, LightProbe, PointLight, RectLight, SpotLight,
24 VolumetricLight,
25};
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
33const DEFAULT_FAR_DEPTH: f32 = 1000.0;
39
40#[derive(Clone, Debug)]
42pub(crate) struct ClusterableObjectAssignmentData {
43 entity: Entity,
44 transform: GlobalTransform,
47 range: f32,
48 object_type: ClusterableObjectType,
49 render_layers: RenderLayers,
50}
51
52impl ClusterableObjectAssignmentData {
53 pub fn sphere(&self) -> Sphere {
54 Sphere {
55 center: self.transform.translation_vec3a(),
56 radius: self.range,
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
64pub enum ClusterableObjectType {
65 PointLight {
67 shadow_maps_enabled: bool,
71
72 volumetric: bool,
76 },
77
78 SpotLight {
80 shadow_maps_enabled: bool,
84
85 volumetric: bool,
89
90 outer_angle: f32,
92 },
93
94 RectLight,
96
97 ReflectionProbe,
99
100 IrradianceVolume,
102
103 Decal,
105}
106
107impl ClusterableObjectType {
108 pub fn ordering(&self) -> (u8, bool, bool) {
116 match *self {
117 ClusterableObjectType::PointLight {
118 shadow_maps_enabled,
119 volumetric,
120 } => (0, !shadow_maps_enabled, !volumetric),
121 ClusterableObjectType::SpotLight {
122 shadow_maps_enabled,
123 volumetric,
124 ..
125 } => (1, !shadow_maps_enabled, !volumetric),
126 ClusterableObjectType::RectLight => (2, false, false),
127 ClusterableObjectType::ReflectionProbe => (3, false, false),
128 ClusterableObjectType::IrradianceVolume => (4, false, false),
129 ClusterableObjectType::Decal => (5, false, false),
130 }
131 }
132}
133
134pub(crate) fn assign_objects_to_clusters(
138 mut views: Query<(
139 &GlobalTransform,
140 &Camera,
141 &Frustum,
142 Option<&ClusterConfig>,
143 &mut Clusters,
144 Option<&RenderLayers>,
145 )>,
146 point_lights_query: Query<(
147 Entity,
148 &GlobalTransform,
149 &ViewVisibility,
150 &PointLight,
151 Option<&RenderLayers>,
152 Option<&VolumetricLight>,
153 )>,
154 spot_lights_query: Query<(
155 Entity,
156 &GlobalTransform,
157 &ViewVisibility,
158 &SpotLight,
159 Option<&RenderLayers>,
160 Option<&VolumetricLight>,
161 )>,
162 rect_lights_query: Query<(
163 Entity,
164 &GlobalTransform,
165 &ViewVisibility,
166 &RectLight,
167 Option<&RenderLayers>,
168 )>,
169 light_probes_query: Query<
170 (
171 Entity,
172 &GlobalTransform,
173 &ViewVisibility,
174 Has<EnvironmentMapLight>,
175 ),
176 With<LightProbe>,
177 >,
178 decals_query: Query<(Entity, &GlobalTransform, &ViewVisibility), With<ClusteredDecal>>,
179 mut clusterable_objects: Local<Vec<ClusterableObjectAssignmentData>>,
180 mut cluster_aabb_spheres: Local<Vec<Option<Sphere>>>,
181 mut max_clusterable_objects_warning_emitted: Local<bool>,
182 global_cluster_settings: Option<Res<GlobalClusterSettings>>,
183) {
184 let Some(global_cluster_settings) = global_cluster_settings else {
185 return;
186 };
187
188 clusterable_objects.clear();
189
190 if global_cluster_settings.gpu_clustering.is_none() {
192 clusterable_objects.extend(point_lights_query.iter().filter_map(
194 |(entity, transform, view_visibility, point_light, maybe_layers, volumetric)| {
195 if view_visibility.get() {
196 Some(ClusterableObjectAssignmentData {
197 entity,
198 transform: GlobalTransform::from_translation(transform.translation()),
199 range: point_light.range,
200 object_type: ClusterableObjectType::PointLight {
201 shadow_maps_enabled: point_light.shadow_maps_enabled,
202 volumetric: volumetric.is_some(),
203 },
204 render_layers: maybe_layers.unwrap_or_default().clone(),
205 })
206 } else {
207 None
208 }
209 },
210 ));
211 clusterable_objects.extend(spot_lights_query.iter().filter_map(
212 |(entity, transform, view_visibility, spot_light, maybe_layers, volumetric)| {
213 if view_visibility.get() {
214 Some(ClusterableObjectAssignmentData {
215 entity,
216 transform: *transform,
217 range: spot_light.range,
218 object_type: ClusterableObjectType::SpotLight {
219 outer_angle: spot_light.outer_angle,
220 shadow_maps_enabled: spot_light.shadow_maps_enabled,
221 volumetric: volumetric.is_some(),
222 },
223 render_layers: maybe_layers.unwrap_or_default().clone(),
224 })
225 } else {
226 None
227 }
228 },
229 ));
230
231 if global_cluster_settings.supports_storage_buffers {
233 clusterable_objects.extend(rect_lights_query.iter().filter_map(
234 |(entity, transform, view_visibility, rect_light, maybe_layers)| {
235 if view_visibility.get() {
236 Some(ClusterableObjectAssignmentData {
237 entity,
238 transform: *transform,
239 range: rect_light.range,
240 object_type: ClusterableObjectType::RectLight,
241 render_layers: maybe_layers.unwrap_or_default().clone(),
242 })
243 } else {
244 None
245 }
246 },
247 ));
248 }
249
250 if global_cluster_settings.supports_storage_buffers {
257 clusterable_objects.extend(light_probes_query.iter().filter_map(
258 |(entity, transform, view_visibility, is_reflection_probe)| {
259 if view_visibility.get() {
260 Some(ClusterableObjectAssignmentData {
261 entity,
262 transform: *transform,
263 range: transform.radius_vec3a(Vec3A::ONE),
264 object_type: if is_reflection_probe {
265 ClusterableObjectType::ReflectionProbe
266 } else {
267 ClusterableObjectType::IrradianceVolume
268 },
269 render_layers: RenderLayers::default(),
270 })
271 } else {
272 None
273 }
274 },
275 ));
276 }
277
278 if global_cluster_settings.clustered_decals_are_usable {
280 clusterable_objects.extend(decals_query.iter().filter_map(
281 |(entity, transform, view_visibility)| {
282 if view_visibility.get() {
283 Some(ClusterableObjectAssignmentData {
284 entity,
285 transform: *transform,
286 range: transform.scale().length(),
287 object_type: ClusterableObjectType::Decal,
288 render_layers: RenderLayers::default(),
289 })
290 } else {
291 None
292 }
293 },
294 ));
295 }
296
297 if clusterable_objects.len()
298 > global_cluster_settings.max_uniform_buffer_clusterable_objects
299 && !global_cluster_settings.supports_storage_buffers
300 {
301 clusterable_objects.sort_by_cached_key(|clusterable_object| {
302 (
303 clusterable_object.object_type.ordering(),
304 clusterable_object.entity,
305 )
306 });
307
308 if clusterable_objects.len()
309 > global_cluster_settings.max_uniform_buffer_clusterable_objects
310 && !*max_clusterable_objects_warning_emitted
311 {
312 warn!(
313 "max_uniform_buffer_clusterable_objects ({}) exceeded",
314 global_cluster_settings.max_uniform_buffer_clusterable_objects
315 );
316 *max_clusterable_objects_warning_emitted = true;
317 }
318
319 clusterable_objects
320 .truncate(global_cluster_settings.max_uniform_buffer_clusterable_objects);
321 }
322 }
323
324 for (camera_transform, camera, frustum, config, clusters, maybe_layers) in &mut views {
325 let view_layers = maybe_layers.unwrap_or_default();
326 let clusters = clusters.into_inner();
327 let config = config.copied().unwrap_or_default();
328
329 if matches!(config, ClusterConfig::None) {
330 clusters.clear(&global_cluster_settings);
331 continue;
332 }
333
334 let screen_size = match camera.physical_viewport_size() {
335 Some(screen_size) if screen_size.x != 0 && screen_size.y != 0 => screen_size,
336 _ => {
337 clusters.clear(&global_cluster_settings);
338 continue;
339 }
340 };
341
342 let mut requested_cluster_dimensions = config.dimensions_for_screen_size(screen_size);
343
344 let world_from_view = camera_transform.affine();
345 let view_from_world_scale = camera_transform.compute_transform().scale.recip();
346 let view_from_world_scale_max = view_from_world_scale.abs().max_element();
347 let view_from_world = Mat4::from(world_from_view.inverse());
348 let is_orthographic = camera.clip_from_view().w_axis.w == 1.0;
349
350 let far_z = match config.far_z_mode() {
351 ClusterFarZMode::MaxClusterableObjectRange => {
352 clusters.last_frame_farthest_z.unwrap_or(DEFAULT_FAR_DEPTH)
353 }
354 ClusterFarZMode::Constant(far) => far,
355 };
356 let first_slice_depth = match (is_orthographic, requested_cluster_dimensions.z) {
357 (true, _) => {
358 (camera.clip_from_view().w_axis.z - 1.0) / camera.clip_from_view().z_axis.z
367 }
368 (false, 1) => config.first_slice_depth().max(far_z),
369 _ => config.first_slice_depth(),
370 };
371 let first_slice_depth = first_slice_depth * view_from_world_scale.z;
372
373 let far_z = far_z.max(first_slice_depth);
375 let cluster_factors = calculate_cluster_factors(
376 first_slice_depth,
377 far_z,
378 requested_cluster_dimensions.z as f32,
379 is_orthographic,
380 );
381
382 if config.dynamic_resizing()
386 && !global_cluster_settings.supports_storage_buffers
387 && let Some(last_frame_cluster_index_count) =
388 clusters.last_frame_total_cluster_index_count
389 && last_frame_cluster_index_count
390 > global_cluster_settings.view_cluster_bindings_max_indices
391 {
392 let index_ratio = global_cluster_settings.view_cluster_bindings_max_indices as f32
399 / last_frame_cluster_index_count as f32;
400 let xy_ratio = index_ratio.sqrt();
401
402 requested_cluster_dimensions.x =
403 ((requested_cluster_dimensions.x as f32 * xy_ratio).floor() as u32).max(1);
404 requested_cluster_dimensions.y =
405 ((requested_cluster_dimensions.y as f32 * xy_ratio).floor() as u32).max(1);
406 }
407
408 clusters.update(screen_size, requested_cluster_dimensions);
409 clusters.near = first_slice_depth;
410 clusters.far = far_z;
411
412 debug_assert!(
414 clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z <= 4096
415 );
416
417 let view_from_clip = camera.clip_from_view().inverse();
418
419 let cluster_count =
420 (clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z) as usize;
421 clusters.reset_for_new_frame(cluster_count, &global_cluster_settings);
422
423 let (mut total_cluster_index_count, mut farthest_z) = (0, 0.0f32);
424 let view_from_world_row_2 = view_from_world.row(2);
425
426 if matches!(clusters.clusterable_objects, ClusterableObjects::Cpu(..)) {
427 cluster_aabb_spheres.clear();
429 cluster_aabb_spheres.extend(core::iter::repeat_n(None, cluster_count));
430
431 let mut x_planes = Vec::with_capacity(clusters.dimensions.x as usize + 1);
433 let mut y_planes = Vec::with_capacity(clusters.dimensions.y as usize + 1);
434 let mut z_planes = Vec::with_capacity(clusters.dimensions.z as usize + 1);
435
436 if is_orthographic {
437 let x_slices = clusters.dimensions.x as f32;
438 for x in 0..=clusters.dimensions.x {
439 let x_proportion = x as f32 / x_slices;
440 let x_pos = x_proportion * 2.0 - 1.0;
441 let view_x = clip_to_view(view_from_clip, Vec4::new(x_pos, 0.0, 1.0, 1.0)).x;
442 let normal = Vec3::X;
443 let d = view_x * normal.x;
444 x_planes.push(HalfSpace::new(normal.extend(d)));
445 }
446
447 let y_slices = clusters.dimensions.y as f32;
448 for y in 0..=clusters.dimensions.y {
449 let y_proportion = 1.0 - y as f32 / y_slices;
450 let y_pos = y_proportion * 2.0 - 1.0;
451 let view_y = clip_to_view(view_from_clip, Vec4::new(0.0, y_pos, 1.0, 1.0)).y;
452 let normal = Vec3::Y;
453 let d = view_y * normal.y;
454 y_planes.push(HalfSpace::new(normal.extend(d)));
455 }
456 } else {
457 let x_slices = clusters.dimensions.x as f32;
458 for x in 0..=clusters.dimensions.x {
459 let x_proportion = x as f32 / x_slices;
460 let x_pos = x_proportion * 2.0 - 1.0;
461 let nb = clip_to_view(view_from_clip, Vec4::new(x_pos, -1.0, 1.0, 1.0)).xyz();
462 let nt = clip_to_view(view_from_clip, Vec4::new(x_pos, 1.0, 1.0, 1.0)).xyz();
463 let normal = nb.cross(nt);
464 let d = nb.dot(normal);
465 x_planes.push(HalfSpace::new(normal.extend(d)));
466 }
467
468 let y_slices = clusters.dimensions.y as f32;
469 for y in 0..=clusters.dimensions.y {
470 let y_proportion = 1.0 - y as f32 / y_slices;
471 let y_pos = y_proportion * 2.0 - 1.0;
472 let nl = clip_to_view(view_from_clip, Vec4::new(-1.0, y_pos, 1.0, 1.0)).xyz();
473 let nr = clip_to_view(view_from_clip, Vec4::new(1.0, y_pos, 1.0, 1.0)).xyz();
474 let normal = nr.cross(nl);
475 let d = nr.dot(normal);
476 y_planes.push(HalfSpace::new(normal.extend(d)));
477 }
478 }
479
480 let z_slices = clusters.dimensions.z;
481 for z in 0..=z_slices {
482 let view_z =
483 z_slice_to_view_z(first_slice_depth, far_z, z_slices, z, is_orthographic);
484 let normal = -Vec3::Z;
485 let d = view_z * normal.z;
486 z_planes.push(HalfSpace::new(normal.extend(d)));
487 }
488
489 for clusterable_object in &clusterable_objects {
490 if !view_layers.intersects(&clusterable_object.render_layers) {
492 continue;
493 }
494
495 let clusterable_object_sphere = clusterable_object.sphere();
496
497 if !frustum.intersects_sphere(&clusterable_object_sphere, true) {
499 continue;
500 }
501
502 let (
504 clusterable_object_aabb_xy_ndc_z_view_min,
505 clusterable_object_aabb_xy_ndc_z_view_max,
506 ) = cluster_space_clusterable_object_aabb(
507 view_from_world,
508 view_from_world_scale,
509 camera.clip_from_view(),
510 &clusterable_object_sphere,
511 );
512
513 let min_cluster = ndc_position_to_cluster(
514 clusters.dimensions,
515 cluster_factors,
516 is_orthographic,
517 clusterable_object_aabb_xy_ndc_z_view_min,
518 clusterable_object_aabb_xy_ndc_z_view_min.z,
519 );
520 let max_cluster = ndc_position_to_cluster(
521 clusters.dimensions,
522 cluster_factors,
523 is_orthographic,
524 clusterable_object_aabb_xy_ndc_z_view_max,
525 clusterable_object_aabb_xy_ndc_z_view_max.z,
526 );
527 let (min_cluster, max_cluster) =
528 (min_cluster.min(max_cluster), min_cluster.max(max_cluster));
529
530 let clusterable_objects = match clusters.clusterable_objects {
534 ClusterableObjects::Gpu => {
535 error!(
536 "We shouldn't be clustering objects on CPU if we're in GPU clustering \
537 mode"
538 );
539 return;
540 }
541 ClusterableObjects::Cpu(ref mut clusterable_objects_cpu) => {
542 clusterable_objects_cpu
543 }
544 };
545
546 let view_clusterable_object_sphere = Sphere {
554 center: Vec3A::from_vec4(
555 view_from_world * clusterable_object_sphere.center.extend(1.0),
556 ),
557 radius: clusterable_object_sphere.radius * view_from_world_scale_max,
558 };
559
560 let this_object_far_z = -view_from_world_row_2
561 .dot(clusterable_object.transform.translation().extend(1.0))
562 + clusterable_object.range * view_from_world_scale.z;
563 farthest_z = farthest_z.max(this_object_far_z);
564
565 let spot_light_dir_sin_cos = match clusterable_object.object_type {
566 ClusterableObjectType::SpotLight { outer_angle, .. } => {
567 let (angle_sin, angle_cos) = sin_cos(outer_angle);
568 Some((
569 (view_from_world * clusterable_object.transform.back().extend(0.0))
570 .truncate()
571 .normalize(),
572 angle_sin,
573 angle_cos,
574 ))
575 }
576 ClusterableObjectType::Decal => {
577 None
579 }
580 ClusterableObjectType::PointLight { .. }
581 | ClusterableObjectType::RectLight
582 | ClusterableObjectType::ReflectionProbe
583 | ClusterableObjectType::IrradianceVolume => None,
584 };
585 let clusterable_object_center_clip =
586 camera.clip_from_view() * view_clusterable_object_sphere.center.extend(1.0);
587 let object_center_ndc =
588 clusterable_object_center_clip.xyz() / clusterable_object_center_clip.w;
589 let cluster_coordinates = ndc_position_to_cluster(
590 clusters.dimensions,
591 cluster_factors,
592 is_orthographic,
593 object_center_ndc,
594 view_clusterable_object_sphere.center.z,
595 );
596 let z_center = if object_center_ndc.z <= 1.0 {
597 Some(cluster_coordinates.z)
598 } else {
599 None
600 };
601 let y_center = if object_center_ndc.y > 1.0 {
602 None
603 } else if object_center_ndc.y < -1.0 {
604 Some(clusters.dimensions.y + 1)
605 } else {
606 Some(cluster_coordinates.y)
607 };
608 for z in min_cluster.z..=max_cluster.z {
609 let mut z_object = view_clusterable_object_sphere;
610 if z_center.is_none() || z != z_center.unwrap() {
611 let z_plane = if z_center.is_some() && z < z_center.unwrap() {
615 z_planes[(z + 1) as usize]
616 } else {
617 z_planes[z as usize]
618 };
619 if let Some(projected) = project_to_plane_z(z_object, z_plane) {
622 z_object = projected;
623 } else {
624 continue;
625 }
626 }
627 for y in min_cluster.y..=max_cluster.y {
628 let mut y_object = z_object;
629 if y_center.is_none() || y != y_center.unwrap() {
630 let y_plane = if y_center.is_some() && y < y_center.unwrap() {
634 y_planes[(y + 1) as usize]
635 } else {
636 y_planes[y as usize]
637 };
638 if let Some(projected) =
641 project_to_plane_y(y_object, y_plane, is_orthographic)
642 {
643 y_object = projected;
644 } else {
645 continue;
646 }
647 }
648 let mut min_x = min_cluster.x;
650 loop {
651 if min_x >= max_cluster.x
652 || -get_distance_x(
653 x_planes[(min_x + 1) as usize],
654 y_object.center,
655 is_orthographic,
656 ) + y_object.radius
657 > 0.0
658 {
659 break;
660 }
661 min_x += 1;
662 }
663 let mut max_x = max_cluster.x;
665 loop {
666 if max_x <= min_x
667 || get_distance_x(
668 x_planes[max_x as usize],
669 y_object.center,
670 is_orthographic,
671 ) + y_object.radius
672 > 0.0
673 {
674 break;
675 }
676 max_x -= 1;
677 }
678 let mut cluster_index = ((y * clusters.dimensions.x + min_x)
679 * clusters.dimensions.z
680 + z) as usize;
681
682 match clusterable_object.object_type {
683 ClusterableObjectType::SpotLight { .. } => {
684 let (view_light_direction, angle_sin, angle_cos) =
685 spot_light_dir_sin_cos.unwrap();
686 for x in min_x..=max_x {
687 let cluster_aabb_sphere =
690 &mut cluster_aabb_spheres[cluster_index];
691 let cluster_aabb_sphere =
692 if let Some(sphere) = cluster_aabb_sphere {
693 &*sphere
694 } else {
695 let aabb = compute_aabb_for_cluster(
696 first_slice_depth,
697 far_z,
698 clusters.tile_size.as_vec2(),
699 screen_size.as_vec2(),
700 view_from_clip,
701 is_orthographic,
702 clusters.dimensions,
703 UVec3::new(x, y, z),
704 );
705 let sphere = Sphere {
706 center: aabb.center,
707 radius: aabb.half_extents.length(),
708 };
709 *cluster_aabb_sphere = Some(sphere);
710 cluster_aabb_sphere.as_ref().unwrap()
711 };
712
713 let spot_light_offset = Vec3::from(
715 view_clusterable_object_sphere.center
716 - cluster_aabb_sphere.center,
717 );
718 let spot_light_dist_sq = spot_light_offset.length_squared();
719 let v1_len = spot_light_offset.dot(view_light_direction);
720
721 let distance_closest_point = (angle_cos
722 * (spot_light_dist_sq - v1_len * v1_len).sqrt())
723 - v1_len * angle_sin;
724 let angle_cull =
725 distance_closest_point > cluster_aabb_sphere.radius;
726
727 let front_cull = v1_len
728 > cluster_aabb_sphere.radius
729 + clusterable_object.range * view_from_world_scale_max;
730 let back_cull = v1_len < -cluster_aabb_sphere.radius;
731
732 if !angle_cull && !front_cull && !back_cull {
733 clusterable_objects[cluster_index]
735 .add_spot_light(clusterable_object.entity);
736 total_cluster_index_count += 1;
737 }
738 cluster_index += clusters.dimensions.z as usize;
739 }
740 }
741
742 ClusterableObjectType::PointLight { .. } => {
743 for _ in min_x..=max_x {
744 clusterable_objects[cluster_index]
746 .add_point_light(clusterable_object.entity);
747 cluster_index += clusters.dimensions.z as usize;
748 }
749 total_cluster_index_count += (max_x - min_x + 1) as usize;
750 }
751
752 ClusterableObjectType::RectLight => {
753 for _ in min_x..=max_x {
755 clusterable_objects[cluster_index]
756 .add_rect_light(clusterable_object.entity);
757 cluster_index += clusters.dimensions.z as usize;
758 }
759 total_cluster_index_count += (max_x - min_x + 1) as usize;
760 }
761
762 ClusterableObjectType::ReflectionProbe => {
763 for _ in min_x..=max_x {
769 clusterable_objects[cluster_index]
770 .add_reflection_probe(clusterable_object.entity);
771 cluster_index += clusters.dimensions.z as usize;
772 }
773 total_cluster_index_count += (max_x - min_x + 1) as usize;
774 }
775
776 ClusterableObjectType::IrradianceVolume => {
777 for _ in min_x..=max_x {
783 clusterable_objects[cluster_index]
784 .add_irradiance_volume(clusterable_object.entity);
785 cluster_index += clusters.dimensions.z as usize;
786 }
787 total_cluster_index_count += (max_x - min_x + 1) as usize;
788 }
789
790 ClusterableObjectType::Decal => {
791 for _ in min_x..=max_x {
797 clusterable_objects[cluster_index]
798 .add_decal(clusterable_object.entity);
799 cluster_index += clusters.dimensions.z as usize;
800 }
801 total_cluster_index_count += (max_x - min_x + 1) as usize;
802 }
803 }
804 }
805 }
806 }
807 }
808
809 clusters.last_frame_total_cluster_index_count = Some(total_cluster_index_count);
813 clusters.last_frame_farthest_z = Some(farthest_z);
814 }
815}
816
817#[expect(missing_docs, reason = "TODO")]
819pub fn calculate_cluster_factors(
820 near: f32,
821 far: f32,
822 z_slices: f32,
823 is_orthographic: bool,
824) -> Vec2 {
825 if is_orthographic {
826 Vec2::new(-near, z_slices / (-far - -near))
827 } else {
828 let z_slices_of_ln_zfar_over_znear = (z_slices - 1.0) / ops::ln(far / near);
829 Vec2::new(
830 z_slices_of_ln_zfar_over_znear,
831 ops::ln(near) * z_slices_of_ln_zfar_over_znear,
832 )
833 }
834}
835
836fn compute_aabb_for_cluster(
837 z_near: f32,
838 z_far: f32,
839 tile_size: Vec2,
840 screen_size: Vec2,
841 view_from_clip: Mat4,
842 is_orthographic: bool,
843 cluster_dimensions: UVec3,
844 ijk: UVec3,
845) -> Aabb {
846 let ijk = ijk.as_vec3();
847
848 let p_min = ijk.xy() * tile_size;
850 let p_max = p_min + tile_size;
851
852 let (cluster_min, cluster_max) = if is_orthographic {
853 let mut p_min = screen_to_view(screen_size, view_from_clip, p_min, 0.0).xyz();
858 let mut p_max = screen_to_view(screen_size, view_from_clip, p_max, 0.0).xyz();
859
860 p_min.z = -z_near + (z_near - z_far) * ijk.z / cluster_dimensions.z as f32;
862 p_max.z = -z_near + (z_near - z_far) * (ijk.z + 1.0) / cluster_dimensions.z as f32;
863
864 (p_min.min(p_max), p_min.max(p_max))
865 } else {
866 let p_min = screen_to_view(screen_size, view_from_clip, p_min, 1.0);
869 let p_max = screen_to_view(screen_size, view_from_clip, p_max, 1.0);
870
871 let z_far_over_z_near = -z_far / -z_near;
872 let cluster_near = if ijk.z == 0.0 {
873 0.0
874 } else {
875 -z_near
876 * ops::powf(
877 z_far_over_z_near,
878 (ijk.z - 1.0) / (cluster_dimensions.z - 1) as f32,
879 )
880 };
881 let cluster_far = if cluster_dimensions.z == 1 {
884 -z_far
885 } else {
886 -z_near * ops::powf(z_far_over_z_near, ijk.z / (cluster_dimensions.z - 1) as f32)
887 };
888
889 let p_min_near = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_near);
891 let p_min_far = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_far);
892 let p_max_near = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_near);
893 let p_max_far = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_far);
894
895 (
896 p_min_near.min(p_min_far).min(p_max_near.min(p_max_far)),
897 p_min_near.max(p_min_far).max(p_max_near.max(p_max_far)),
898 )
899 };
900
901 Aabb::from_min_max(cluster_min, cluster_max)
902}
903
904fn z_slice_to_view_z(
906 near: f32,
907 far: f32,
908 z_slices: u32,
909 z_slice: u32,
910 is_orthographic: bool,
911) -> f32 {
912 if is_orthographic {
913 return -near - (far - near) * z_slice as f32 / z_slices as f32;
914 }
915
916 if z_slice == 0 {
918 0.0
919 } else {
920 -near * ops::powf(far / near, (z_slice - 1) as f32 / (z_slices - 1) as f32)
921 }
922}
923
924fn ndc_position_to_cluster(
925 cluster_dimensions: UVec3,
926 cluster_factors: Vec2,
927 is_orthographic: bool,
928 ndc_p: Vec3,
929 view_z: f32,
930) -> UVec3 {
931 let cluster_dimensions_f32 = cluster_dimensions.as_vec3();
932 let frag_coord = (ndc_p.xy() * VEC2_HALF_NEGATIVE_Y + VEC2_HALF).clamp(Vec2::ZERO, Vec2::ONE);
933 let xy = (frag_coord * cluster_dimensions_f32.xy()).floor();
934 let z_slice = view_z_to_z_slice(
935 cluster_factors,
936 cluster_dimensions.z,
937 view_z,
938 is_orthographic,
939 );
940 xy.as_uvec2()
941 .extend(z_slice)
942 .clamp(UVec3::ZERO, cluster_dimensions - UVec3::ONE)
943}
944
945fn cluster_space_clusterable_object_aabb(
951 view_from_world: Mat4,
952 view_from_world_scale: Vec3,
953 clip_from_view: Mat4,
954 clusterable_object_sphere: &Sphere,
955) -> (Vec3, Vec3) {
956 let clusterable_object_aabb_view = Aabb {
957 center: Vec3A::from_vec4(view_from_world * clusterable_object_sphere.center.extend(1.0)),
958 half_extents: Vec3A::from(clusterable_object_sphere.radius * view_from_world_scale.abs()),
959 };
960 let (mut clusterable_object_aabb_view_min, mut clusterable_object_aabb_view_max) = (
961 clusterable_object_aabb_view.min(),
962 clusterable_object_aabb_view.max(),
963 );
964
965 clusterable_object_aabb_view_min.z = clusterable_object_aabb_view_min.z.min(-f32::MIN_POSITIVE);
973 clusterable_object_aabb_view_max.z = clusterable_object_aabb_view_max.z.min(-f32::MIN_POSITIVE);
974
975 let (
980 clusterable_object_aabb_view_xymin_near,
981 clusterable_object_aabb_view_xymin_far,
982 clusterable_object_aabb_view_xymax_near,
983 clusterable_object_aabb_view_xymax_far,
984 ) = (
985 clusterable_object_aabb_view_min,
986 clusterable_object_aabb_view_min
987 .xy()
988 .extend(clusterable_object_aabb_view_max.z),
989 clusterable_object_aabb_view_max
990 .xy()
991 .extend(clusterable_object_aabb_view_min.z),
992 clusterable_object_aabb_view_max,
993 );
994 let (
995 clusterable_object_aabb_clip_xymin_near,
996 clusterable_object_aabb_clip_xymin_far,
997 clusterable_object_aabb_clip_xymax_near,
998 clusterable_object_aabb_clip_xymax_far,
999 ) = (
1000 clip_from_view * clusterable_object_aabb_view_xymin_near.extend(1.0),
1001 clip_from_view * clusterable_object_aabb_view_xymin_far.extend(1.0),
1002 clip_from_view * clusterable_object_aabb_view_xymax_near.extend(1.0),
1003 clip_from_view * clusterable_object_aabb_view_xymax_far.extend(1.0),
1004 );
1005 let (
1006 clusterable_object_aabb_ndc_xymin_near,
1007 clusterable_object_aabb_ndc_xymin_far,
1008 clusterable_object_aabb_ndc_xymax_near,
1009 clusterable_object_aabb_ndc_xymax_far,
1010 ) = (
1011 clusterable_object_aabb_clip_xymin_near.xyz() / clusterable_object_aabb_clip_xymin_near.w,
1012 clusterable_object_aabb_clip_xymin_far.xyz() / clusterable_object_aabb_clip_xymin_far.w,
1013 clusterable_object_aabb_clip_xymax_near.xyz() / clusterable_object_aabb_clip_xymax_near.w,
1014 clusterable_object_aabb_clip_xymax_far.xyz() / clusterable_object_aabb_clip_xymax_far.w,
1015 );
1016 let (clusterable_object_aabb_ndc_min, clusterable_object_aabb_ndc_max) = (
1017 clusterable_object_aabb_ndc_xymin_near
1018 .min(clusterable_object_aabb_ndc_xymin_far)
1019 .min(clusterable_object_aabb_ndc_xymax_near)
1020 .min(clusterable_object_aabb_ndc_xymax_far),
1021 clusterable_object_aabb_ndc_xymin_near
1022 .max(clusterable_object_aabb_ndc_xymin_far)
1023 .max(clusterable_object_aabb_ndc_xymax_near)
1024 .max(clusterable_object_aabb_ndc_xymax_far),
1025 );
1026
1027 let (aabb_min_ndc, aabb_max_ndc) = (
1029 clusterable_object_aabb_ndc_min.xy().clamp(NDC_MIN, NDC_MAX),
1030 clusterable_object_aabb_ndc_max.xy().clamp(NDC_MIN, NDC_MAX),
1031 );
1032
1033 (
1035 aabb_min_ndc.extend(clusterable_object_aabb_view_min.z),
1036 aabb_max_ndc.extend(clusterable_object_aabb_view_max.z),
1037 )
1038}
1039
1040fn line_intersection_to_z_plane(origin: Vec3, p: Vec3, z: f32) -> Vec3 {
1042 let v = p - origin;
1043 let t = (z - Vec3::Z.dot(origin)) / Vec3::Z.dot(v);
1044 origin + t * v
1045}
1046
1047fn view_z_to_z_slice(
1049 cluster_factors: Vec2,
1050 z_slices: u32,
1051 view_z: f32,
1052 is_orthographic: bool,
1053) -> u32 {
1054 let z_slice = if is_orthographic {
1055 ((view_z - cluster_factors.x) * cluster_factors.y).floor() as u32
1057 } else {
1058 (ops::ln(-view_z) * cluster_factors.x - cluster_factors.y + 1.0) as u32
1060 };
1061 z_slice.min(z_slices - 1)
1064}
1065
1066fn clip_to_view(view_from_clip: Mat4, clip: Vec4) -> Vec4 {
1067 let view = view_from_clip * clip;
1068 view / view.w
1069}
1070
1071fn screen_to_view(screen_size: Vec2, view_from_clip: Mat4, screen: Vec2, ndc_z: f32) -> Vec4 {
1072 let tex_coord = screen / screen_size;
1073 let clip = Vec4::new(
1074 tex_coord.x * 2.0 - 1.0,
1075 (1.0 - tex_coord.y) * 2.0 - 1.0,
1076 ndc_z,
1077 1.0,
1078 );
1079 clip_to_view(view_from_clip, clip)
1080}
1081
1082fn get_distance_x(plane: HalfSpace, point: Vec3A, is_orthographic: bool) -> f32 {
1084 if is_orthographic {
1085 point.x - plane.d()
1086 } else {
1087 plane.normal_d().xz().dot(point.xz())
1092 }
1093}
1094
1095fn project_to_plane_z(z_object: Sphere, z_plane: HalfSpace) -> Option<Sphere> {
1097 let z = z_plane.d() / z_plane.normal_d().z;
1105 let distance_to_plane = z - z_object.center.z;
1106 if distance_to_plane.abs() > z_object.radius {
1107 return None;
1108 }
1109 Some(Sphere {
1110 center: Vec3A::from(z_object.center.xy().extend(z)),
1111 radius: (z_object.radius * z_object.radius - distance_to_plane * distance_to_plane).sqrt(),
1114 })
1115}
1116
1117fn project_to_plane_y(
1119 y_object: Sphere,
1120 y_plane: HalfSpace,
1121 is_orthographic: bool,
1122) -> Option<Sphere> {
1123 let distance_to_plane = if is_orthographic {
1124 y_plane.d() - y_object.center.y
1125 } else {
1126 -y_object.center.yz().dot(y_plane.normal_d().yz())
1127 };
1128
1129 if distance_to_plane.abs() > y_object.radius {
1130 return None;
1131 }
1132 Some(Sphere {
1133 center: y_object.center + distance_to_plane * y_plane.normal(),
1134 radius: (y_object.radius * y_object.radius - distance_to_plane * distance_to_plane).sqrt(),
1135 })
1136}