diff --git a/crates/bevy_pbr/src/cluster/assign.rs b/crates/bevy_pbr/src/cluster/assign.rs index c6bef3426c9fa..9727659bf67cc 100644 --- a/crates/bevy_pbr/src/cluster/assign.rs +++ b/crates/bevy_pbr/src/cluster/assign.rs @@ -16,9 +16,9 @@ use bevy_transform::components::GlobalTransform; use bevy_utils::tracing::warn; use crate::{ - ClusterConfig, ClusterFarZMode, Clusters, GlobalVisiblePointLights, PointLight, SpotLight, - ViewClusterBindings, VisiblePointLights, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, - MAX_UNIFORM_BUFFER_POINT_LIGHTS, + ClusterConfig, ClusterFarZMode, Clusters, GlobalVisibleClusterableObjects, PointLight, + SpotLight, ViewClusterBindings, VisibleClusterableObjects, + CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS, }; const NDC_MIN: Vec2 = Vec2::NEG_ONE; @@ -28,8 +28,8 @@ const VEC2_HALF: Vec2 = Vec2::splat(0.5); const VEC2_HALF_NEGATIVE_Y: Vec2 = Vec2::new(0.5, -0.5); #[derive(Clone)] -// data required for assigning lights to clusters -pub(crate) struct PointLightAssignmentData { +// data required for assigning objects to clusters +pub(crate) struct ClusterableObjectAssignmentData { entity: Entity, transform: GlobalTransform, range: f32, @@ -38,7 +38,7 @@ pub(crate) struct PointLightAssignmentData { render_layers: RenderLayers, } -impl PointLightAssignmentData { +impl ClusterableObjectAssignmentData { pub fn sphere(&self) -> Sphere { Sphere { center: self.transform.translation_vec3a(), @@ -49,9 +49,9 @@ impl PointLightAssignmentData { // NOTE: Run this before update_point_light_frusta! #[allow(clippy::too_many_arguments)] -pub(crate) fn assign_lights_to_clusters( +pub(crate) fn assign_objects_to_clusters( mut commands: Commands, - mut global_lights: ResMut, + mut global_clusterable_objects: ResMut, mut views: Query<( Entity, &GlobalTransform, @@ -60,7 +60,7 @@ pub(crate) fn assign_lights_to_clusters( &ClusterConfig, &mut Clusters, Option<&RenderLayers>, - Option<&mut VisiblePointLights>, + Option<&mut VisibleClusterableObjects>, )>, point_lights_query: Query<( Entity, @@ -76,25 +76,25 @@ pub(crate) fn assign_lights_to_clusters( Option<&RenderLayers>, &ViewVisibility, )>, - mut lights: Local>, + mut clusterable_objects: Local>, mut cluster_aabb_spheres: Local>>, - mut max_point_lights_warning_emitted: Local, + mut max_clusterable_objects_warning_emitted: Local, render_device: Option>, ) { let Some(render_device) = render_device else { return; }; - global_lights.entities.clear(); - lights.clear(); - // collect just the relevant light query data into a persisted vec to avoid reallocating each frame - lights.extend( + global_clusterable_objects.entities.clear(); + clusterable_objects.clear(); + // collect just the relevant query data into a persisted vec to avoid reallocating each frame + clusterable_objects.extend( point_lights_query .iter() .filter(|(.., visibility)| visibility.get()) .map( |(entity, transform, point_light, maybe_layers, _visibility)| { - PointLightAssignmentData { + ClusterableObjectAssignmentData { entity, transform: GlobalTransform::from_translation(transform.translation()), shadows_enabled: point_light.shadows_enabled, @@ -105,13 +105,13 @@ pub(crate) fn assign_lights_to_clusters( }, ), ); - lights.extend( + clusterable_objects.extend( spot_lights_query .iter() .filter(|(.., visibility)| visibility.get()) .map( |(entity, transform, spot_light, maybe_layers, _visibility)| { - PointLightAssignmentData { + ClusterableObjectAssignmentData { entity, transform: *transform, shadows_enabled: spot_light.shadows_enabled, @@ -129,55 +129,60 @@ pub(crate) fn assign_lights_to_clusters( clustered_forward_buffer_binding_type, BufferBindingType::Storage { .. } ); - if lights.len() > MAX_UNIFORM_BUFFER_POINT_LIGHTS && !supports_storage_buffers { - lights.sort_by(|light_1, light_2| { - crate::point_light_order( + if clusterable_objects.len() > MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + && !supports_storage_buffers + { + clusterable_objects.sort_by(|clusterable_object_1, clusterable_object_2| { + crate::clusterable_object_order( ( - &light_1.entity, - &light_1.shadows_enabled, - &light_1.spot_light_angle.is_some(), + &clusterable_object_1.entity, + &clusterable_object_1.shadows_enabled, + &clusterable_object_1.spot_light_angle.is_some(), ), ( - &light_2.entity, - &light_2.shadows_enabled, - &light_2.spot_light_angle.is_some(), + &clusterable_object_2.entity, + &clusterable_object_2.shadows_enabled, + &clusterable_object_2.spot_light_angle.is_some(), ), ) }); - // check each light against each view's frustum, keep only those that affect at least one of our views + // check each clusterable object against each view's frustum, keep only + // those that affect at least one of our views let frusta: Vec<_> = views .iter() .map(|(_, _, _, frustum, _, _, _, _)| *frustum) .collect(); - let mut lights_in_view_count = 0; - lights.retain(|light| { - // take one extra light to check if we should emit the warning - if lights_in_view_count == MAX_UNIFORM_BUFFER_POINT_LIGHTS + 1 { + let mut clusterable_objects_in_view_count = 0; + clusterable_objects.retain(|clusterable_object| { + // take one extra clusterable object to check if we should emit the warning + if clusterable_objects_in_view_count == MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + 1 { false } else { - let light_sphere = light.sphere(); - let light_in_view = frusta + let clusterable_object_sphere = clusterable_object.sphere(); + let clusterable_object_in_view = frusta .iter() - .any(|frustum| frustum.intersects_sphere(&light_sphere, true)); + .any(|frustum| frustum.intersects_sphere(&clusterable_object_sphere, true)); - if light_in_view { - lights_in_view_count += 1; + if clusterable_object_in_view { + clusterable_objects_in_view_count += 1; } - light_in_view + clusterable_object_in_view } }); - if lights.len() > MAX_UNIFORM_BUFFER_POINT_LIGHTS && !*max_point_lights_warning_emitted { + if clusterable_objects.len() > MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + && !*max_clusterable_objects_warning_emitted + { warn!( - "MAX_UNIFORM_BUFFER_POINT_LIGHTS ({}) exceeded", - MAX_UNIFORM_BUFFER_POINT_LIGHTS + "MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS ({}) exceeded", + MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS ); - *max_point_lights_warning_emitted = true; + *max_clusterable_objects_warning_emitted = true; } - lights.truncate(MAX_UNIFORM_BUFFER_POINT_LIGHTS); + clusterable_objects.truncate(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS); } for ( @@ -188,15 +193,17 @@ pub(crate) fn assign_lights_to_clusters( config, clusters, maybe_layers, - mut visible_lights, + mut visible_clusterable_objects, ) in &mut views { let view_layers = maybe_layers.unwrap_or_default(); let clusters = clusters.into_inner(); if matches!(config, ClusterConfig::None) { - if visible_lights.is_some() { - commands.entity(view_entity).remove::(); + if visible_clusterable_objects.is_some() { + commands + .entity(view_entity) + .remove::(); } clusters.clear(); continue; @@ -216,13 +223,13 @@ pub(crate) fn assign_lights_to_clusters( let is_orthographic = camera.clip_from_view().w_axis.w == 1.0; let far_z = match config.far_z_mode() { - ClusterFarZMode::MaxLightRange => { + ClusterFarZMode::MaxClusterableObjectRange => { let view_from_world_row_2 = view_from_world.row(2); - lights + clusterable_objects .iter() - .map(|light| { - -view_from_world_row_2.dot(light.transform.translation().extend(1.0)) - + light.range * view_from_world_scale.z + .map(|object| { + -view_from_world_row_2.dot(object.transform.translation().extend(1.0)) + + object.range * view_from_world_scale.z }) .reduce(f32::max) .unwrap_or(0.0) @@ -257,43 +264,44 @@ pub(crate) fn assign_lights_to_clusters( if config.dynamic_resizing() { let mut cluster_index_estimate = 0.0; - for light in &lights { - let light_sphere = light.sphere(); + for clusterable_object in &clusterable_objects { + let clusterable_object_sphere = clusterable_object.sphere(); - // Check if the light is within the view frustum - if !frustum.intersects_sphere(&light_sphere, true) { + // Check if the clusterable object is within the view frustum + if !frustum.intersects_sphere(&clusterable_object_sphere, true) { continue; } // calculate a conservative aabb estimate of number of clusters affected by this light // this overestimates index counts by at most 50% (and typically much less) when the whole light range is in view // it can overestimate more significantly when light ranges are only partially in view - let (light_aabb_min, light_aabb_max) = cluster_space_light_aabb( - view_from_world, - view_from_world_scale, - camera.clip_from_view(), - &light_sphere, - ); + let (clusterable_object_aabb_min, clusterable_object_aabb_max) = + cluster_space_clusterable_object_aabb( + view_from_world, + view_from_world_scale, + camera.clip_from_view(), + &clusterable_object_sphere, + ); // since we won't adjust z slices we can calculate exact number of slices required in z dimension let z_cluster_min = view_z_to_z_slice( cluster_factors, requested_cluster_dimensions.z, - light_aabb_min.z, + clusterable_object_aabb_min.z, is_orthographic, ); let z_cluster_max = view_z_to_z_slice( cluster_factors, requested_cluster_dimensions.z, - light_aabb_max.z, + clusterable_object_aabb_max.z, is_orthographic, ); let z_count = z_cluster_min.max(z_cluster_max) - z_cluster_min.min(z_cluster_max) + 1; // calculate x/y count using floats to avoid overestimating counts due to large initial tile sizes - let xy_min = light_aabb_min.xy(); - let xy_max = light_aabb_max.xy(); + let xy_min = clusterable_object_aabb_min.xy(); + let xy_max = clusterable_object_aabb_max.xy(); // multiply by 0.5 to move from [-1,1] to [-0.5, 0.5], max extent of 1 in each dimension let xy_count = (xy_max - xy_min) * 0.5 @@ -339,16 +347,16 @@ pub(crate) fn assign_lights_to_clusters( let view_from_clip = camera.clip_from_view().inverse(); - for lights in &mut clusters.lights { - lights.entities.clear(); - lights.point_light_count = 0; - lights.spot_light_count = 0; + for clusterable_objects in &mut clusters.clusterable_objects { + clusterable_objects.entities.clear(); + clusterable_objects.point_light_count = 0; + clusterable_objects.spot_light_count = 0; } let cluster_count = (clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z) as usize; clusters - .lights - .resize_with(cluster_count, VisiblePointLights::default); + .clusterable_objects + .resize_with(cluster_count, VisibleClusterableObjects::default); // initialize empty cluster bounding spheres cluster_aabb_spheres.clear(); @@ -411,46 +419,53 @@ pub(crate) fn assign_lights_to_clusters( z_planes.push(HalfSpace::new(normal.extend(d))); } - let mut update_from_light_intersections = |visible_lights: &mut Vec| { - for light in &lights { - // check if the light layers overlap the view layers - if !view_layers.intersects(&light.render_layers) { + let mut update_from_object_intersections = |visible_clusterable_objects: &mut Vec< + Entity, + >| { + for clusterable_object in &clusterable_objects { + // check if the clusterable light layers overlap the view layers + if !view_layers.intersects(&clusterable_object.render_layers) { continue; } - let light_sphere = light.sphere(); + let clusterable_object_sphere = clusterable_object.sphere(); - // Check if the light is within the view frustum - if !frustum.intersects_sphere(&light_sphere, true) { + // Check if the clusterable object is within the view frustum + if !frustum.intersects_sphere(&clusterable_object_sphere, true) { continue; } - // NOTE: The light intersects the frustum so it must be visible and part of the global set - global_lights.entities.insert(light.entity); - visible_lights.push(light.entity); + // NOTE: The clusterable object intersects the frustum so it + // must be visible and part of the global set + global_clusterable_objects + .entities + .insert(clusterable_object.entity); + visible_clusterable_objects.push(clusterable_object.entity); // note: caching seems to be slower than calling twice for this aabb calculation - let (light_aabb_xy_ndc_z_view_min, light_aabb_xy_ndc_z_view_max) = - cluster_space_light_aabb( - view_from_world, - view_from_world_scale, - camera.clip_from_view(), - &light_sphere, - ); + let ( + clusterable_object_aabb_xy_ndc_z_view_min, + clusterable_object_aabb_xy_ndc_z_view_max, + ) = cluster_space_clusterable_object_aabb( + view_from_world, + view_from_world_scale, + camera.clip_from_view(), + &clusterable_object_sphere, + ); let min_cluster = ndc_position_to_cluster( clusters.dimensions, cluster_factors, is_orthographic, - light_aabb_xy_ndc_z_view_min, - light_aabb_xy_ndc_z_view_min.z, + clusterable_object_aabb_xy_ndc_z_view_min, + clusterable_object_aabb_xy_ndc_z_view_min.z, ); let max_cluster = ndc_position_to_cluster( clusters.dimensions, cluster_factors, is_orthographic, - light_aabb_xy_ndc_z_view_max, - light_aabb_xy_ndc_z_view_max.z, + clusterable_object_aabb_xy_ndc_z_view_max, + clusterable_object_aabb_xy_ndc_z_view_max.z, ); let (min_cluster, max_cluster) = (min_cluster.min(max_cluster), min_cluster.max(max_cluster)); @@ -462,47 +477,51 @@ pub(crate) fn assign_lights_to_clusters( // stretched and warped, which prevents simpler algorithms from being correct // as they often assume that the widest part of the sphere under projection is the // center point on the axis of interest plus the radius, and that is not true! - let view_light_sphere = Sphere { - center: Vec3A::from(view_from_world * light_sphere.center.extend(1.0)), - radius: light_sphere.radius * view_from_world_scale_max, + let view_clusterable_object_sphere = Sphere { + center: Vec3A::from( + view_from_world * clusterable_object_sphere.center.extend(1.0), + ), + radius: clusterable_object_sphere.radius * view_from_world_scale_max, }; - let spot_light_dir_sin_cos = light.spot_light_angle.map(|angle| { + let spot_light_dir_sin_cos = clusterable_object.spot_light_angle.map(|angle| { let (angle_sin, angle_cos) = angle.sin_cos(); ( - (view_from_world * light.transform.back().extend(0.0)) + (view_from_world * clusterable_object.transform.back().extend(0.0)) .truncate() .normalize(), angle_sin, angle_cos, ) }); - let light_center_clip = - camera.clip_from_view() * view_light_sphere.center.extend(1.0); - let light_center_ndc = light_center_clip.xyz() / light_center_clip.w; + let clusterable_object_center_clip = + camera.clip_from_view() * view_clusterable_object_sphere.center.extend(1.0); + let object_center_ndc = + clusterable_object_center_clip.xyz() / clusterable_object_center_clip.w; let cluster_coordinates = ndc_position_to_cluster( clusters.dimensions, cluster_factors, is_orthographic, - light_center_ndc, - view_light_sphere.center.z, + object_center_ndc, + view_clusterable_object_sphere.center.z, ); - let z_center = if light_center_ndc.z <= 1.0 { + let z_center = if object_center_ndc.z <= 1.0 { Some(cluster_coordinates.z) } else { None }; - let y_center = if light_center_ndc.y > 1.0 { + let y_center = if object_center_ndc.y > 1.0 { None - } else if light_center_ndc.y < -1.0 { + } else if object_center_ndc.y < -1.0 { Some(clusters.dimensions.y + 1) } else { Some(cluster_coordinates.y) }; for z in min_cluster.z..=max_cluster.z { - let mut z_light = view_light_sphere.clone(); + let mut z_object = view_clusterable_object_sphere.clone(); if z_center.is_none() || z != z_center.unwrap() { - // The z plane closer to the light has the larger radius circle where the - // light sphere intersects the z plane. + // The z plane closer to the clusterable object has the + // larger radius circle where the light sphere + // intersects the z plane. let z_plane = if z_center.is_some() && z < z_center.unwrap() { z_planes[(z + 1) as usize] } else { @@ -510,17 +529,18 @@ pub(crate) fn assign_lights_to_clusters( }; // Project the sphere to this z plane and use its radius as the radius of a // new, refined sphere. - if let Some(projected) = project_to_plane_z(z_light, z_plane) { - z_light = projected; + if let Some(projected) = project_to_plane_z(z_object, z_plane) { + z_object = projected; } else { continue; } } for y in min_cluster.y..=max_cluster.y { - let mut y_light = z_light.clone(); + let mut y_object = z_object.clone(); if y_center.is_none() || y != y_center.unwrap() { - // The y plane closer to the light has the larger radius circle where the - // light sphere intersects the y plane. + // The y plane closer to the clusterable object has + // the larger radius circle where the light sphere + // intersects the y plane. let y_plane = if y_center.is_some() && y < y_center.unwrap() { y_planes[(y + 1) as usize] } else { @@ -529,9 +549,9 @@ pub(crate) fn assign_lights_to_clusters( // Project the refined sphere to this y plane and use its radius as the // radius of a new, even more refined sphere. if let Some(projected) = - project_to_plane_y(y_light, y_plane, is_orthographic) + project_to_plane_y(y_object, y_plane, is_orthographic) { - y_light = projected; + y_object = projected; } else { continue; } @@ -542,9 +562,9 @@ pub(crate) fn assign_lights_to_clusters( if min_x >= max_cluster.x || -get_distance_x( x_planes[(min_x + 1) as usize], - y_light.center, + y_object.center, is_orthographic, - ) + y_light.radius + ) + y_object.radius > 0.0 { break; @@ -557,9 +577,9 @@ pub(crate) fn assign_lights_to_clusters( if max_x <= min_x || get_distance_x( x_planes[max_x as usize], - y_light.center, + y_object.center, is_orthographic, - ) + y_light.radius + ) + y_object.radius > 0.0 { break; @@ -601,7 +621,8 @@ pub(crate) fn assign_lights_to_clusters( // test -- based on https://bartwronski.com/2017/04/13/cull-that-cone/ let spot_light_offset = Vec3::from( - view_light_sphere.center - cluster_aabb_sphere.center, + view_clusterable_object_sphere.center + - cluster_aabb_sphere.center, ); let spot_light_dist_sq = spot_light_offset.length_squared(); let v1_len = spot_light_offset.dot(view_light_direction); @@ -614,21 +635,26 @@ pub(crate) fn assign_lights_to_clusters( let front_cull = v1_len > cluster_aabb_sphere.radius - + light.range * view_from_world_scale_max; + + clusterable_object.range * view_from_world_scale_max; let back_cull = v1_len < -cluster_aabb_sphere.radius; if !angle_cull && !front_cull && !back_cull { // this cluster is affected by the spot light - clusters.lights[cluster_index].entities.push(light.entity); - clusters.lights[cluster_index].spot_light_count += 1; + clusters.clusterable_objects[cluster_index] + .entities + .push(clusterable_object.entity); + clusters.clusterable_objects[cluster_index].spot_light_count += + 1; } cluster_index += clusters.dimensions.z as usize; } } else { for _ in min_x..=max_x { // all clusters within range are affected by point lights - clusters.lights[cluster_index].entities.push(light.entity); - clusters.lights[cluster_index].point_light_count += 1; + clusters.clusterable_objects[cluster_index] + .entities + .push(clusterable_object.entity); + clusters.clusterable_objects[cluster_index].point_light_count += 1; cluster_index += clusters.dimensions.z as usize; } } @@ -637,17 +663,19 @@ pub(crate) fn assign_lights_to_clusters( } }; - // reuse existing visible lights Vec, if it exists - if let Some(visible_lights) = visible_lights.as_mut() { - visible_lights.entities.clear(); - update_from_light_intersections(&mut visible_lights.entities); + // reuse existing visible clusterable objects Vec, if it exists + if let Some(visible_clusterable_objects) = visible_clusterable_objects.as_mut() { + visible_clusterable_objects.entities.clear(); + update_from_object_intersections(&mut visible_clusterable_objects.entities); } else { let mut entities = Vec::new(); - update_from_light_intersections(&mut entities); - commands.entity(view_entity).insert(VisiblePointLights { - entities, - ..Default::default() - }); + update_from_object_intersections(&mut entities); + commands + .entity(view_entity) + .insert(VisibleClusterableObjects { + entities, + ..Default::default() + }); } } } @@ -759,22 +787,24 @@ fn ndc_position_to_cluster( .clamp(UVec3::ZERO, cluster_dimensions - UVec3::ONE) } -/// Calculate bounds for the light using a view space aabb. +/// Calculate bounds for the clusterable object using a view space aabb. /// Returns a `(Vec3, Vec3)` containing minimum and maximum with /// `X` and `Y` in normalized device coordinates with range `[-1, 1]` /// `Z` in view space, with range `[-inf, -f32::MIN_POSITIVE]` -fn cluster_space_light_aabb( +fn cluster_space_clusterable_object_aabb( view_from_world: Mat4, view_from_world_scale: Vec3, clip_from_view: Mat4, - light_sphere: &Sphere, + clusterable_object_sphere: &Sphere, ) -> (Vec3, Vec3) { - let light_aabb_view = Aabb { - center: Vec3A::from(view_from_world * light_sphere.center.extend(1.0)), - half_extents: Vec3A::from(light_sphere.radius * view_from_world_scale.abs()), + let clusterable_object_aabb_view = Aabb { + center: Vec3A::from(view_from_world * clusterable_object_sphere.center.extend(1.0)), + half_extents: Vec3A::from(clusterable_object_sphere.radius * view_from_world_scale.abs()), }; - let (mut light_aabb_view_min, mut light_aabb_view_max) = - (light_aabb_view.min(), light_aabb_view.max()); + let (mut clusterable_object_aabb_view_min, mut clusterable_object_aabb_view_max) = ( + clusterable_object_aabb_view.min(), + clusterable_object_aabb_view.max(), + ); // Constrain view z to be negative - i.e. in front of the camera // When view z is >= 0.0 and we're using a perspective projection, bad things happen. @@ -783,67 +813,71 @@ fn cluster_space_light_aabb( // use of min/max operations as something that was to the left in view space is now returning a // coordinate that for view z in front of the camera would be on the right, but at view z behind the // camera is on the left. So, we just constrain view z to be < 0.0 and necessarily in front of the camera. - light_aabb_view_min.z = light_aabb_view_min.z.min(-f32::MIN_POSITIVE); - light_aabb_view_max.z = light_aabb_view_max.z.min(-f32::MIN_POSITIVE); + clusterable_object_aabb_view_min.z = clusterable_object_aabb_view_min.z.min(-f32::MIN_POSITIVE); + clusterable_object_aabb_view_max.z = clusterable_object_aabb_view_max.z.min(-f32::MIN_POSITIVE); // Is there a cheaper way to do this? The problem is that because of perspective // the point at max z but min xy may be less xy in screenspace, and similar. As // such, projecting the min and max xy at both the closer and further z and taking // the min and max of those projected points addresses this. let ( - light_aabb_view_xymin_near, - light_aabb_view_xymin_far, - light_aabb_view_xymax_near, - light_aabb_view_xymax_far, + clusterable_object_aabb_view_xymin_near, + clusterable_object_aabb_view_xymin_far, + clusterable_object_aabb_view_xymax_near, + clusterable_object_aabb_view_xymax_far, ) = ( - light_aabb_view_min, - light_aabb_view_min.xy().extend(light_aabb_view_max.z), - light_aabb_view_max.xy().extend(light_aabb_view_min.z), - light_aabb_view_max, + clusterable_object_aabb_view_min, + clusterable_object_aabb_view_min + .xy() + .extend(clusterable_object_aabb_view_max.z), + clusterable_object_aabb_view_max + .xy() + .extend(clusterable_object_aabb_view_min.z), + clusterable_object_aabb_view_max, ); let ( - light_aabb_clip_xymin_near, - light_aabb_clip_xymin_far, - light_aabb_clip_xymax_near, - light_aabb_clip_xymax_far, + clusterable_object_aabb_clip_xymin_near, + clusterable_object_aabb_clip_xymin_far, + clusterable_object_aabb_clip_xymax_near, + clusterable_object_aabb_clip_xymax_far, ) = ( - clip_from_view * light_aabb_view_xymin_near.extend(1.0), - clip_from_view * light_aabb_view_xymin_far.extend(1.0), - clip_from_view * light_aabb_view_xymax_near.extend(1.0), - clip_from_view * light_aabb_view_xymax_far.extend(1.0), + clip_from_view * clusterable_object_aabb_view_xymin_near.extend(1.0), + clip_from_view * clusterable_object_aabb_view_xymin_far.extend(1.0), + clip_from_view * clusterable_object_aabb_view_xymax_near.extend(1.0), + clip_from_view * clusterable_object_aabb_view_xymax_far.extend(1.0), ); let ( - light_aabb_ndc_xymin_near, - light_aabb_ndc_xymin_far, - light_aabb_ndc_xymax_near, - light_aabb_ndc_xymax_far, + clusterable_object_aabb_ndc_xymin_near, + clusterable_object_aabb_ndc_xymin_far, + clusterable_object_aabb_ndc_xymax_near, + clusterable_object_aabb_ndc_xymax_far, ) = ( - light_aabb_clip_xymin_near.xyz() / light_aabb_clip_xymin_near.w, - light_aabb_clip_xymin_far.xyz() / light_aabb_clip_xymin_far.w, - light_aabb_clip_xymax_near.xyz() / light_aabb_clip_xymax_near.w, - light_aabb_clip_xymax_far.xyz() / light_aabb_clip_xymax_far.w, + clusterable_object_aabb_clip_xymin_near.xyz() / clusterable_object_aabb_clip_xymin_near.w, + clusterable_object_aabb_clip_xymin_far.xyz() / clusterable_object_aabb_clip_xymin_far.w, + clusterable_object_aabb_clip_xymax_near.xyz() / clusterable_object_aabb_clip_xymax_near.w, + clusterable_object_aabb_clip_xymax_far.xyz() / clusterable_object_aabb_clip_xymax_far.w, ); - let (light_aabb_ndc_min, light_aabb_ndc_max) = ( - light_aabb_ndc_xymin_near - .min(light_aabb_ndc_xymin_far) - .min(light_aabb_ndc_xymax_near) - .min(light_aabb_ndc_xymax_far), - light_aabb_ndc_xymin_near - .max(light_aabb_ndc_xymin_far) - .max(light_aabb_ndc_xymax_near) - .max(light_aabb_ndc_xymax_far), + let (clusterable_object_aabb_ndc_min, clusterable_object_aabb_ndc_max) = ( + clusterable_object_aabb_ndc_xymin_near + .min(clusterable_object_aabb_ndc_xymin_far) + .min(clusterable_object_aabb_ndc_xymax_near) + .min(clusterable_object_aabb_ndc_xymax_far), + clusterable_object_aabb_ndc_xymin_near + .max(clusterable_object_aabb_ndc_xymin_far) + .max(clusterable_object_aabb_ndc_xymax_near) + .max(clusterable_object_aabb_ndc_xymax_far), ); // clamp to ndc coords without depth let (aabb_min_ndc, aabb_max_ndc) = ( - light_aabb_ndc_min.xy().clamp(NDC_MIN, NDC_MAX), - light_aabb_ndc_max.xy().clamp(NDC_MIN, NDC_MAX), + clusterable_object_aabb_ndc_min.xy().clamp(NDC_MIN, NDC_MAX), + clusterable_object_aabb_ndc_max.xy().clamp(NDC_MIN, NDC_MAX), ); // pack unadjusted z depth into the vecs ( - aabb_min_ndc.extend(light_aabb_view_min.z), - aabb_max_ndc.extend(light_aabb_view_max.z), + aabb_min_ndc.extend(clusterable_object_aabb_view_min.z), + aabb_max_ndc.extend(clusterable_object_aabb_view_max.z), ) } @@ -903,7 +937,7 @@ fn get_distance_x(plane: HalfSpace, point: Vec3A, is_orthographic: bool) -> f32 } // NOTE: This exploits the fact that a z-plane normal has only a z component -fn project_to_plane_z(z_light: Sphere, z_plane: HalfSpace) -> Option { +fn project_to_plane_z(z_object: Sphere, z_plane: HalfSpace) -> Option { // p = sphere center // n = plane normal // d = n.p if p is in the plane @@ -912,35 +946,35 @@ fn project_to_plane_z(z_light: Sphere, z_plane: HalfSpace) -> Option { // = pz * nz // => pz = d / nz let z = z_plane.d() / z_plane.normal_d().z; - let distance_to_plane = z - z_light.center.z; - if distance_to_plane.abs() > z_light.radius { + let distance_to_plane = z - z_object.center.z; + if distance_to_plane.abs() > z_object.radius { return None; } Some(Sphere { - center: Vec3A::from(z_light.center.xy().extend(z)), + center: Vec3A::from(z_object.center.xy().extend(z)), // hypotenuse length = radius // pythagoras = (distance to plane)^2 + b^2 = radius^2 - radius: (z_light.radius * z_light.radius - distance_to_plane * distance_to_plane).sqrt(), + radius: (z_object.radius * z_object.radius - distance_to_plane * distance_to_plane).sqrt(), }) } // NOTE: This exploits the fact that a y-plane normal has only y and z components fn project_to_plane_y( - y_light: Sphere, + y_object: Sphere, y_plane: HalfSpace, is_orthographic: bool, ) -> Option { let distance_to_plane = if is_orthographic { - y_plane.d() - y_light.center.y + y_plane.d() - y_object.center.y } else { - -y_light.center.yz().dot(y_plane.normal_d().yz()) + -y_object.center.yz().dot(y_plane.normal_d().yz()) }; - if distance_to_plane.abs() > y_light.radius { + if distance_to_plane.abs() > y_object.radius { return None; } Some(Sphere { - center: y_light.center + distance_to_plane * y_plane.normal(), - radius: (y_light.radius * y_light.radius - distance_to_plane * distance_to_plane).sqrt(), + center: y_object.center + distance_to_plane * y_plane.normal(), + radius: (y_object.radius * y_object.radius - distance_to_plane * distance_to_plane).sqrt(), }) } diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index a8c218b4475b3..a0d628474db8e 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -23,7 +23,7 @@ use bevy_render::{ }; use bevy_utils::{hashbrown::HashSet, tracing::warn}; -pub(crate) use crate::cluster::assign::assign_lights_to_clusters; +pub(crate) use crate::cluster::assign::assign_objects_to_clusters; use crate::MeshPipeline; mod assign; @@ -32,14 +32,14 @@ mod assign; mod test; // NOTE: this must be kept in sync with the same constants in pbr.frag -pub const MAX_UNIFORM_BUFFER_POINT_LIGHTS: usize = 256; +pub const MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS: usize = 256; // NOTE: Clustered-forward rendering requires 3 storage buffer bindings so check that // at least that many are supported using this constant and SupportedBindingType::from_device() pub const CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT: u32 = 3; // this must match CLUSTER_COUNT_SIZE in pbr.wgsl -// and must be large enough to contain MAX_UNIFORM_BUFFER_POINT_LIGHTS +// and must be large enough to contain MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS const CLUSTER_COUNT_SIZE: u32 = 9; const CLUSTER_OFFSET_MASK: u32 = (1 << (32 - (CLUSTER_COUNT_SIZE * 2))) - 1; @@ -58,11 +58,11 @@ const CLUSTER_COUNT_MASK: u32 = (1 << CLUSTER_COUNT_SIZE) - 1; /// rendering #[derive(Debug, Copy, Clone, Reflect)] pub enum ClusterFarZMode { - /// Calculate the required maximum z-depth based on currently visible lights. - /// Makes better use of available clusters, speeding up GPU lighting operations - /// at the expense of some CPU time and using more indices in the cluster light - /// index lists. - MaxLightRange, + /// Calculate the required maximum z-depth based on currently visible + /// clusterable objects. Makes better use of available clusters, speeding + /// up GPU lighting operations at the expense of some CPU time and using + /// more indices in the clusterable object index lists. + MaxClusterableObjectRange, /// Constant max z-depth Constant(f32), } @@ -81,7 +81,7 @@ pub struct ClusterZConfig { #[derive(Debug, Copy, Clone, Component, Reflect)] #[reflect(Component)] pub enum ClusterConfig { - /// Disable light cluster calculations for this view + /// Disable cluster calculations for this view None, /// One single cluster. Optimal for low-light complexity scenes or scenes where /// most lights affect the entire scene. @@ -91,7 +91,7 @@ pub enum ClusterConfig { dimensions: UVec3, z_config: ClusterZConfig, /// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding - /// the available cluster-light index limit + /// the available cluster-object index limit dynamic_resizing: bool, }, /// Fixed number of `Z` slices, `X` and `Y` calculated to give square clusters @@ -104,7 +104,7 @@ pub enum ClusterConfig { z_slices: u32, z_config: ClusterZConfig, /// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding - /// the available cluster-light index limit + /// the available clusterable object index limit dynamic_resizing: bool, }, } @@ -119,29 +119,29 @@ pub struct Clusters { /// and explicitly-configured to avoid having unnecessarily many slices close to the camera. pub(crate) near: f32, pub(crate) far: f32, - pub(crate) lights: Vec, + pub(crate) clusterable_objects: Vec, } #[derive(Clone, Component, Debug, Default)] -pub struct VisiblePointLights { +pub struct VisibleClusterableObjects { pub(crate) entities: Vec, pub point_light_count: usize, pub spot_light_count: usize, } #[derive(Resource, Default)] -pub struct GlobalVisiblePointLights { +pub struct GlobalVisibleClusterableObjects { pub(crate) entities: HashSet, } #[derive(Resource)] -pub struct GlobalLightMeta { - pub gpu_point_lights: GpuPointLights, +pub struct GlobalClusterableObjectMeta { + pub gpu_clusterable_objects: GpuClusterableObjects, pub entity_to_index: EntityHashMap, } #[derive(Copy, Clone, ShaderType, Default, Debug)] -pub struct GpuPointLight { +pub struct GpuClusterableObject { // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3] // For spot lights: 2 components of the direction (x,z), spot_scale and spot_offset pub(crate) light_custom_data: Vec4, @@ -153,20 +153,20 @@ pub struct GpuPointLight { pub(crate) spot_light_tan_angle: f32, } -pub enum GpuPointLights { - Uniform(UniformBuffer), - Storage(StorageBuffer), +pub enum GpuClusterableObjects { + Uniform(UniformBuffer), + Storage(StorageBuffer), } #[derive(ShaderType)] -pub struct GpuPointLightsUniform { - data: Box<[GpuPointLight; MAX_UNIFORM_BUFFER_POINT_LIGHTS]>, +pub struct GpuClusterableObjectsUniform { + data: Box<[GpuClusterableObject; MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS]>, } #[derive(ShaderType, Default)] -pub struct GpuPointLightsStorage { +pub struct GpuClusterableObjectsStorage { #[size(runtime)] - data: Vec, + data: Vec, } #[derive(Component)] @@ -178,14 +178,14 @@ pub struct ExtractedClusterConfig { pub(crate) dimensions: UVec3, } -enum ExtractedClustersPointLightsElement { +enum ExtractedClusterableObjectElement { ClusterHeader(u32, u32), - LightEntity(Entity), + ClusterableObjectEntity(Entity), } #[derive(Component)] -pub struct ExtractedClustersPointLights { - data: Vec, +pub struct ExtractedClusterableObjects { + data: Vec, } #[derive(ShaderType)] @@ -194,7 +194,7 @@ struct GpuClusterOffsetsAndCountsUniform { } #[derive(ShaderType, Default)] -struct GpuClusterLightIndexListsStorage { +struct GpuClusterableObjectIndexListsStorage { #[size(runtime)] data: Vec, } @@ -208,12 +208,12 @@ struct GpuClusterOffsetsAndCountsStorage { enum ViewClusterBuffers { Uniform { // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment - cluster_light_index_lists: UniformBuffer, + clusterable_object_index_lists: UniformBuffer, // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment cluster_offsets_and_counts: UniformBuffer, }, Storage { - cluster_light_index_lists: StorageBuffer, + clusterable_object_index_lists: StorageBuffer, cluster_offsets_and_counts: StorageBuffer, }, } @@ -229,7 +229,7 @@ impl Default for ClusterZConfig { fn default() -> Self { Self { first_slice_depth: 5.0, - far_z_mode: ClusterFarZMode::MaxLightRange, + far_z_mode: ClusterFarZMode::MaxClusterableObjectRange, } } } @@ -297,7 +297,7 @@ impl ClusterConfig { fn far_z_mode(&self) -> ClusterFarZMode { match self { ClusterConfig::None => ClusterFarZMode::Constant(0.0), - ClusterConfig::Single => ClusterFarZMode::MaxLightRange, + ClusterConfig::Single => ClusterFarZMode::MaxClusterableObjectRange, ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => { z_config.far_z_mode } @@ -342,7 +342,7 @@ impl Clusters { self.dimensions = UVec3::ZERO; self.near = 0.0; self.far = 0.0; - self.lights.clear(); + self.clusterable_objects.clear(); } } @@ -356,14 +356,15 @@ pub fn add_clusters( } let config = config.copied().unwrap_or_default(); - // actual settings here don't matter - they will be overwritten in assign_lights_to_clusters + // actual settings here don't matter - they will be overwritten in + // `assign_objects_to_clusters`` commands .entity(entity) .insert((Clusters::default(), config)); } } -impl VisiblePointLights { +impl VisibleClusterableObjects { #[inline] pub fn iter(&self) -> impl DoubleEndedIterator { self.entities.iter() @@ -380,7 +381,7 @@ impl VisiblePointLights { } } -impl GlobalVisiblePointLights { +impl GlobalVisibleClusterableObjects { #[inline] pub fn iter(&self) -> impl Iterator { self.entities.iter() @@ -392,7 +393,7 @@ impl GlobalVisiblePointLights { } } -impl FromWorld for GlobalLightMeta { +impl FromWorld for GlobalClusterableObjectMeta { fn from_world(world: &mut World) -> Self { Self::new( world @@ -402,16 +403,16 @@ impl FromWorld for GlobalLightMeta { } } -impl GlobalLightMeta { +impl GlobalClusterableObjectMeta { pub fn new(buffer_binding_type: BufferBindingType) -> Self { Self { - gpu_point_lights: GpuPointLights::new(buffer_binding_type), + gpu_clusterable_objects: GpuClusterableObjects::new(buffer_binding_type), entity_to_index: EntityHashMap::default(), } } } -impl GpuPointLights { +impl GpuClusterableObjects { fn new(buffer_binding_type: BufferBindingType) -> Self { match buffer_binding_type { BufferBindingType::Storage { .. } => Self::storage(), @@ -427,17 +428,19 @@ impl GpuPointLights { Self::Storage(StorageBuffer::default()) } - pub(crate) fn set(&mut self, mut lights: Vec) { + pub(crate) fn set(&mut self, mut clusterable_objects: Vec) { match self { - GpuPointLights::Uniform(buffer) => { - let len = lights.len().min(MAX_UNIFORM_BUFFER_POINT_LIGHTS); - let src = &lights[..len]; + GpuClusterableObjects::Uniform(buffer) => { + let len = clusterable_objects + .len() + .min(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS); + let src = &clusterable_objects[..len]; let dst = &mut buffer.get_mut().data[..len]; dst.copy_from_slice(src); } - GpuPointLights::Storage(buffer) => { + GpuClusterableObjects::Storage(buffer) => { buffer.get_mut().data.clear(); - buffer.get_mut().data.append(&mut lights); + buffer.get_mut().data.append(&mut clusterable_objects); } } } @@ -448,41 +451,54 @@ impl GpuPointLights { render_queue: &RenderQueue, ) { match self { - GpuPointLights::Uniform(buffer) => buffer.write_buffer(render_device, render_queue), - GpuPointLights::Storage(buffer) => buffer.write_buffer(render_device, render_queue), + GpuClusterableObjects::Uniform(buffer) => { + buffer.write_buffer(render_device, render_queue); + } + GpuClusterableObjects::Storage(buffer) => { + buffer.write_buffer(render_device, render_queue); + } } } pub fn binding(&self) -> Option { match self { - GpuPointLights::Uniform(buffer) => buffer.binding(), - GpuPointLights::Storage(buffer) => buffer.binding(), + GpuClusterableObjects::Uniform(buffer) => buffer.binding(), + GpuClusterableObjects::Storage(buffer) => buffer.binding(), } } pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZeroU64 { match buffer_binding_type { - BufferBindingType::Storage { .. } => GpuPointLightsStorage::min_size(), - BufferBindingType::Uniform => GpuPointLightsUniform::min_size(), + BufferBindingType::Storage { .. } => GpuClusterableObjectsStorage::min_size(), + BufferBindingType::Uniform => GpuClusterableObjectsUniform::min_size(), } } } -impl Default for GpuPointLightsUniform { +impl Default for GpuClusterableObjectsUniform { fn default() -> Self { Self { - data: Box::new([GpuPointLight::default(); MAX_UNIFORM_BUFFER_POINT_LIGHTS]), + data: Box::new( + [GpuClusterableObject::default(); MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS], + ), } } } #[allow(clippy::too_many_arguments)] -// Sort lights by -// - point-light vs spot-light, so that we can iterate point lights and spot lights in contiguous blocks in the fragment shader, -// - then those with shadows enabled first, so that the index can be used to render at most `point_light_shadow_maps_count` -// point light shadows and `spot_light_shadow_maps_count` spot light shadow maps, -// - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded. -pub(crate) fn point_light_order( +// Sort clusterable objects by: +// +// * point-light vs spot-light, so that we can iterate point lights and spot +// lights in contiguous blocks in the fragment shader, +// +// * then those with shadows enabled first, so that the index can be used to +// render at most `point_light_shadow_maps_count` point light shadows and +// `spot_light_shadow_maps_count` spot light shadow maps, +// +// * then by entity as a stable key to ensure that a consistent set of +// clusterable objects are chosen if the clusterable object count limit is +// exceeded. +pub(crate) fn clusterable_object_order( (entity_1, shadows_enabled_1, is_spot_light_1): (&Entity, &bool, &bool), (entity_2, shadows_enabled_2, is_spot_light_2): (&Entity, &bool, &bool), ) -> std::cmp::Ordering { @@ -502,20 +518,26 @@ pub fn extract_clusters( continue; } - let num_entities: usize = clusters.lights.iter().map(|l| l.entities.len()).sum(); - let mut data = Vec::with_capacity(clusters.lights.len() + num_entities); - for cluster_lights in &clusters.lights { - data.push(ExtractedClustersPointLightsElement::ClusterHeader( - cluster_lights.point_light_count as u32, - cluster_lights.spot_light_count as u32, + let num_entities: usize = clusters + .clusterable_objects + .iter() + .map(|l| l.entities.len()) + .sum(); + let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + num_entities); + for cluster_objects in &clusters.clusterable_objects { + data.push(ExtractedClusterableObjectElement::ClusterHeader( + cluster_objects.point_light_count as u32, + cluster_objects.spot_light_count as u32, )); - for l in &cluster_lights.entities { - data.push(ExtractedClustersPointLightsElement::LightEntity(*l)); + for clusterable_entity in &cluster_objects.entities { + data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity( + *clusterable_entity, + )); } } commands.get_or_spawn(entity).insert(( - ExtractedClustersPointLights { data }, + ExtractedClusterableObjects { data }, ExtractedClusterConfig { near: clusters.near, far: clusters.far, @@ -530,8 +552,8 @@ pub fn prepare_clusters( render_device: Res, render_queue: Res, mesh_pipeline: Res, - global_light_meta: Res, - views: Query<(Entity, &ExtractedClustersPointLights)>, + global_clusterable_object_meta: Res, + views: Query<(Entity, &ExtractedClusterableObjects)>, ) { let render_device = render_device.into_inner(); let supports_storage_buffers = matches!( @@ -545,7 +567,7 @@ pub fn prepare_clusters( for record in &extracted_clusters.data { match record { - ExtractedClustersPointLightsElement::ClusterHeader( + ExtractedClusterableObjectElement::ClusterHeader( point_light_count, spot_light_count, ) => { @@ -556,15 +578,20 @@ pub fn prepare_clusters( *spot_light_count as usize, ); } - ExtractedClustersPointLightsElement::LightEntity(entity) => { - if let Some(light_index) = global_light_meta.entity_to_index.get(entity) { + ExtractedClusterableObjectElement::ClusterableObjectEntity(entity) => { + if let Some(clusterable_object_index) = + global_clusterable_object_meta.entity_to_index.get(entity) + { if view_clusters_bindings.n_indices() >= ViewClusterBindings::MAX_INDICES && !supports_storage_buffers { - warn!("Cluster light index lists is full! The PointLights in the view are affecting too many clusters."); + warn!( + "Clusterable object index lists are full! The clusterable \ + objects in the view are present in too many clusters." + ); break; } - view_clusters_bindings.push_index(*light_index); + view_clusters_bindings.push_index(*clusterable_object_index); } } } @@ -592,18 +619,19 @@ impl ViewClusterBindings { pub fn clear(&mut self) { match &mut self.buffers { ViewClusterBuffers::Uniform { - cluster_light_index_lists, + clusterable_object_index_lists, cluster_offsets_and_counts, } => { - *cluster_light_index_lists.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]; + *clusterable_object_index_lists.get_mut().data = + [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]; *cluster_offsets_and_counts.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]; } ViewClusterBuffers::Storage { - cluster_light_index_lists, + clusterable_object_index_lists, cluster_offsets_and_counts, .. } => { - cluster_light_index_lists.get_mut().data.clear(); + clusterable_object_index_lists.get_mut().data.clear(); cluster_offsets_and_counts.get_mut().data.clear(); } } @@ -648,7 +676,7 @@ impl ViewClusterBindings { pub fn push_index(&mut self, index: usize) { match &mut self.buffers { ViewClusterBuffers::Uniform { - cluster_light_index_lists, + clusterable_object_index_lists, .. } => { let array_index = self.n_indices >> 4; // >> 4 is equivalent to / 16 @@ -656,14 +684,17 @@ impl ViewClusterBindings { let sub_index = self.n_indices & ((1 << 2) - 1); let index = index as u32; - cluster_light_index_lists.get_mut().data[array_index][component] |= + clusterable_object_index_lists.get_mut().data[array_index][component] |= index << (8 * sub_index); } ViewClusterBuffers::Storage { - cluster_light_index_lists, + clusterable_object_index_lists, .. } => { - cluster_light_index_lists.get_mut().data.push(index as u32); + clusterable_object_index_lists + .get_mut() + .data + .push(index as u32); } } @@ -673,32 +704,32 @@ impl ViewClusterBindings { pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { match &mut self.buffers { ViewClusterBuffers::Uniform { - cluster_light_index_lists, + clusterable_object_index_lists, cluster_offsets_and_counts, } => { - cluster_light_index_lists.write_buffer(render_device, render_queue); + clusterable_object_index_lists.write_buffer(render_device, render_queue); cluster_offsets_and_counts.write_buffer(render_device, render_queue); } ViewClusterBuffers::Storage { - cluster_light_index_lists, + clusterable_object_index_lists, cluster_offsets_and_counts, } => { - cluster_light_index_lists.write_buffer(render_device, render_queue); + clusterable_object_index_lists.write_buffer(render_device, render_queue); cluster_offsets_and_counts.write_buffer(render_device, render_queue); } } } - pub fn light_index_lists_binding(&self) -> Option { + pub fn clusterable_object_index_lists_binding(&self) -> Option { match &self.buffers { ViewClusterBuffers::Uniform { - cluster_light_index_lists, + clusterable_object_index_lists, .. - } => cluster_light_index_lists.binding(), + } => clusterable_object_index_lists.binding(), ViewClusterBuffers::Storage { - cluster_light_index_lists, + clusterable_object_index_lists, .. - } => cluster_light_index_lists.binding(), + } => clusterable_object_index_lists.binding(), } } @@ -715,12 +746,12 @@ impl ViewClusterBindings { } } - pub fn min_size_cluster_light_index_lists( + pub fn min_size_clusterable_object_index_lists( buffer_binding_type: BufferBindingType, ) -> NonZeroU64 { match buffer_binding_type { - BufferBindingType::Storage { .. } => GpuClusterLightIndexListsStorage::min_size(), - BufferBindingType::Uniform => GpuClusterLightIndexListsUniform::min_size(), + BufferBindingType::Storage { .. } => GpuClusterableObjectIndexListsStorage::min_size(), + BufferBindingType::Uniform => GpuClusterableObjectIndexListsUniform::min_size(), } } @@ -744,21 +775,21 @@ impl ViewClusterBuffers { fn uniform() -> Self { ViewClusterBuffers::Uniform { - cluster_light_index_lists: UniformBuffer::default(), + clusterable_object_index_lists: UniformBuffer::default(), cluster_offsets_and_counts: UniformBuffer::default(), } } fn storage() -> Self { ViewClusterBuffers::Storage { - cluster_light_index_lists: StorageBuffer::default(), + clusterable_object_index_lists: StorageBuffer::default(), cluster_offsets_and_counts: StorageBuffer::default(), } } } // NOTE: With uniform buffer max binding size as 16384 bytes -// that means we can fit 256 point lights in one uniform +// that means we can fit 256 clusterable objects in one uniform // buffer, which means the count can be at most 256 so it // needs 9 bits. // The array of indices can also use u8 and that means the @@ -778,15 +809,15 @@ fn pack_offset_and_counts(offset: usize, point_count: usize, spot_count: usize) } #[derive(ShaderType)] -struct GpuClusterLightIndexListsUniform { +struct GpuClusterableObjectIndexListsUniform { data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>, } -// NOTE: Assert at compile time that GpuClusterLightIndexListsUniform +// NOTE: Assert at compile time that GpuClusterableObjectIndexListsUniform // fits within the maximum uniform buffer binding size -const _: () = assert!(GpuClusterLightIndexListsUniform::SHADER_SIZE.get() <= 16384); +const _: () = assert!(GpuClusterableObjectIndexListsUniform::SHADER_SIZE.get() <= 16384); -impl Default for GpuClusterLightIndexListsUniform { +impl Default for GpuClusterableObjectIndexListsUniform { fn default() -> Self { Self { data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]), diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 8d26a98c5bd7b..6bd4751931eea 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -298,7 +298,7 @@ impl Plugin for PbrPlugin { .register_type::() .register_type::() .init_resource::() - .init_resource::() + .init_resource::() .init_resource::() .init_resource::() .register_type::() @@ -339,7 +339,7 @@ impl Plugin for PbrPlugin { PostUpdate, ( add_clusters.in_set(SimulationLightSystems::AddClusters), - crate::assign_lights_to_clusters + crate::assign_objects_to_clusters .in_set(SimulationLightSystems::AssignLightsToClusters) .after(TransformSystem::TransformPropagate) .after(VisibilitySystems::CheckVisibility) @@ -427,7 +427,7 @@ impl Plugin for PbrPlugin { // Extract the required data from the main world render_app .init_resource::() - .init_resource::(); + .init_resource::(); } } diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index 2da2ddde1f540..4273de71d5cfe 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -561,7 +561,7 @@ pub fn update_directional_light_frusta( // NOTE: Run this after assign_lights_to_clusters! pub fn update_point_light_frusta( - global_lights: Res, + global_lights: Res, mut views: Query< (Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta), Or<(Changed, Changed)>, @@ -605,7 +605,7 @@ pub fn update_point_light_frusta( } pub fn update_spot_light_frusta( - global_lights: Res, + global_lights: Res, mut views: Query< (Entity, &GlobalTransform, &SpotLight, &mut Frustum), Or<(Changed, Changed)>, @@ -639,7 +639,7 @@ pub fn update_spot_light_frusta( } pub fn check_light_mesh_visibility( - visible_point_lights: Query<&VisiblePointLights>, + visible_point_lights: Query<&VisibleClusterableObjects>, mut point_lights: Query<( &PointLight, &GlobalTransform, diff --git a/crates/bevy_pbr/src/render/clustered_forward.wgsl b/crates/bevy_pbr/src/render/clustered_forward.wgsl index 56525248963e3..5da176ab19cd8 100644 --- a/crates/bevy_pbr/src/render/clustered_forward.wgsl +++ b/crates/bevy_pbr/src/render/clustered_forward.wgsl @@ -53,13 +53,14 @@ fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { #endif } -fn get_light_id(index: u32) -> u32 { +fn get_clusterable_object_id(index: u32) -> u32 { #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 - return bindings::cluster_light_index_lists.data[index]; + return bindings::clusterable_object_index_lists.data[index]; #else - // The index is correct but in cluster_light_index_lists we pack 4 u8s into a u32 - // This means the index into cluster_light_index_lists is index / 4 - let indices = bindings::cluster_light_index_lists.data[index >> 4u][(index >> 2u) & ((1u << 2u) - 1u)]; + // The index is correct but in clusterable_object_index_lists we pack 4 u8s into a u32 + // This means the index into clusterable_object_index_lists is index / 4 + let indices = bindings::clusterable_object_index_lists.data[index >> 4u][(index >> 2u) & + ((1u << 2u) - 1u)]; // And index % 4 gives the sub-index of the u8 within the u32 so we shift by 8 * sub-index return (indices >> (8u * (index & ((1u << 2u) - 1u)))) & ((1u << 8u) - 1u); #endif @@ -93,14 +94,23 @@ fn cluster_debug_visualization( output_color.a ); #endif // CLUSTERED_FORWARD_DEBUG_Z_SLICES -#ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY - // NOTE: This debug mode visualises the number of lights within the cluster that contains - // the fragment. It shows a sort of lighting complexity measure. +#ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_COMPLEXITY + // NOTE: This debug mode visualises the number of clusterable objects within + // the cluster that contains the fragment. It shows a sort of cluster + // complexity measure. let cluster_overlay_alpha = 0.1; - let max_light_complexity_per_cluster = 64.0; - output_color.r = (1.0 - cluster_overlay_alpha) * output_color.r + cluster_overlay_alpha * smoothStep(0.0, max_light_complexity_per_cluster, f32(offset_and_counts[1] + offset_and_counts[2])); - output_color.g = (1.0 - cluster_overlay_alpha) * output_color.g + cluster_overlay_alpha * (1.0 - smoothStep(0.0, max_light_complexity_per_cluster, f32(offset_and_counts[1] + offset_and_counts[2]))); -#endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY + let max_complexity_per_cluster = 64.0; + output_color.r = (1.0 - cluster_overlay_alpha) * output_color.r + cluster_overlay_alpha * + smoothStep( + 0.0, + max_complexity_per_cluster, + f32(offset_and_counts[1] + offset_and_counts[2])); + output_color.g = (1.0 - cluster_overlay_alpha) * output_color.g + cluster_overlay_alpha * + (1.0 - smoothStep( + 0.0, + max_complexity_per_cluster, + f32(offset_and_counts[1] + offset_and_counts[2]))); +#endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_COMPLEXITY #ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY // NOTE: Visualizes the cluster to which the fragment belongs let cluster_overlay_alpha = 0.1; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 88ccb462d3b13..ded628c5b33ee 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -173,7 +173,7 @@ pub fn extract_lights( mut commands: Commands, point_light_shadow_map: Extract>, directional_light_shadow_map: Extract>, - global_point_lights: Extract>, + global_point_lights: Extract>, point_lights: Extract< Query<( &PointLight, @@ -513,7 +513,7 @@ pub fn prepare_lights( mut texture_cache: ResMut, render_device: Res, render_queue: Res, - mut global_light_meta: ResMut, + mut global_light_meta: ResMut, mut light_meta: ResMut, views: Query<( Entity, @@ -634,7 +634,7 @@ pub fn prepare_lights( // point light shadows and `spot_light_shadow_maps_count` spot light shadow maps, // - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded. point_lights.sort_by(|(entity_1, light_1, _), (entity_2, light_2, _)| { - crate::cluster::point_light_order( + crate::cluster::clusterable_object_order( ( entity_1, &light_1.shadows_enabled, @@ -714,7 +714,7 @@ pub fn prepare_lights( } }; - gpu_point_lights.push(GpuPointLight { + gpu_point_lights.push(GpuClusterableObject { light_custom_data, // premultiply color by intensity // we don't use the alpha at all, so no reason to multiply only [0..3] @@ -779,9 +779,11 @@ pub fn prepare_lights( } } - global_light_meta.gpu_point_lights.set(gpu_point_lights); global_light_meta - .gpu_point_lights + .gpu_clusterable_objects + .set(gpu_point_lights); + global_light_meta + .gpu_clusterable_objects .write_buffer(&render_device, &render_queue); live_shadow_mapping_lights.clear(); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 9a5867ba9b3d6..465fac8011896 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -41,9 +41,9 @@ use crate::{ self, IrradianceVolume, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, }, - prepass, FogMeta, GlobalLightMeta, GpuFog, GpuLights, GpuPointLights, LightMeta, - LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, RenderViewLightProbes, - ScreenSpaceAmbientOcclusionTextures, ScreenSpaceReflectionsBuffer, + prepass, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights, + LightMeta, LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, + RenderViewLightProbes, ScreenSpaceAmbientOcclusionTextures, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, }; @@ -229,13 +229,13 @@ fn layout_entries( ), // Directional Shadow Texture Array Sampler (5, sampler(SamplerBindingType::Comparison)), - // PointLights + // ClusterableObjects ( 6, buffer_layout( clustered_forward_buffer_binding_type, false, - Some(GpuPointLights::min_size( + Some(GpuClusterableObjects::min_size( clustered_forward_buffer_binding_type, )), ), @@ -246,9 +246,11 @@ fn layout_entries( buffer_layout( clustered_forward_buffer_binding_type, false, - Some(ViewClusterBindings::min_size_cluster_light_index_lists( - clustered_forward_buffer_binding_type, - )), + Some( + ViewClusterBindings::min_size_clusterable_object_index_lists( + clustered_forward_buffer_binding_type, + ), + ), ), ), // ClusterOffsetsAndCounts @@ -446,7 +448,7 @@ pub fn prepare_mesh_view_bind_groups( mesh_pipeline: Res, shadow_samplers: Res, light_meta: Res, - global_light_meta: Res, + global_light_meta: Res, fog_meta: Res, view_uniforms: Res, views: Query<( @@ -476,7 +478,7 @@ pub fn prepare_mesh_view_bind_groups( if let ( Some(view_binding), Some(light_binding), - Some(point_light_binding), + Some(clusterable_objects_binding), Some(globals), Some(fog_binding), Some(light_probes_binding), @@ -485,7 +487,7 @@ pub fn prepare_mesh_view_bind_groups( ) = ( view_uniforms.uniforms.binding(), light_meta.view_gpu_lights.binding(), - global_light_meta.gpu_point_lights.binding(), + global_light_meta.gpu_clusterable_objects.binding(), globals_buffer.buffer.binding(), fog_meta.gpu_fogs.binding(), light_probes_buffer.binding(), @@ -524,8 +526,13 @@ pub fn prepare_mesh_view_bind_groups( (3, &shadow_samplers.point_light_sampler), (4, &shadow_bindings.directional_light_depth_texture_view), (5, &shadow_samplers.directional_light_sampler), - (6, point_light_binding.clone()), - (7, cluster_bindings.light_index_lists_binding().unwrap()), + (6, clusterable_objects_binding.clone()), + ( + 7, + cluster_bindings + .clusterable_object_index_lists_binding() + .unwrap(), + ), (8, cluster_bindings.offsets_and_counts_binding().unwrap()), (9, globals.clone()), (10, fog_binding.clone()), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index de3e5c3b46a55..f5fcd34271582 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -22,12 +22,12 @@ @group(0) @binding(5) var directional_shadow_textures_sampler: sampler_comparison; #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 -@group(0) @binding(6) var point_lights: types::PointLights; -@group(0) @binding(7) var cluster_light_index_lists: types::ClusterLightIndexLists; +@group(0) @binding(6) var clusterable_objects: types::ClusterableObjects; +@group(0) @binding(7) var clusterable_object_index_lists: types::ClusterLightIndexLists; @group(0) @binding(8) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #else -@group(0) @binding(6) var point_lights: types::PointLights; -@group(0) @binding(7) var cluster_light_index_lists: types::ClusterLightIndexLists; +@group(0) @binding(6) var clusterable_objects: types::ClusterableObjects; +@group(0) @binding(7) var clusterable_object_index_lists: types::ClusterLightIndexLists; @group(0) @binding(8) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; #endif diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index e294aa5b5cf66..def738b3e28d5 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -1,6 +1,6 @@ #define_import_path bevy_pbr::mesh_view_types -struct PointLight { +struct ClusterableObject { // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3] // For spot lights: the direction (x,z), spot_scale and spot_offset light_custom_data: vec4, @@ -88,8 +88,8 @@ const FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3u; const FOG_MODE_ATMOSPHERIC: u32 = 4u; #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 -struct PointLights { - data: array, +struct ClusterableObjects { + data: array, }; struct ClusterLightIndexLists { data: array, @@ -98,11 +98,11 @@ struct ClusterOffsetsAndCounts { data: array>, }; #else -struct PointLights { - data: array, +struct ClusterableObjects { + data: array, }; struct ClusterLightIndexLists { - // each u32 contains 4 u8 indices into the PointLights array + // each u32 contains 4 u8 indices into the ClusterableObjects array data: array, 1024u>, }; struct ClusterOffsetsAndCounts { diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 51b4dc4c83cf0..5ba5de85b9276 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -410,10 +410,10 @@ fn apply_pbr_lighting( // Point lights (direct) for (var i: u32 = offset_and_counts[0]; i < offset_and_counts[0] + offset_and_counts[1]; i = i + 1u) { - let light_id = clustering::get_light_id(i); + let light_id = clustering::get_clusterable_object_id(i); var shadow: f32 = 1.0; if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal); } @@ -432,7 +432,7 @@ fn apply_pbr_lighting( // F0 = vec3(0.0) var transmitted_shadow: f32 = 1.0; if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) - && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); } @@ -444,11 +444,11 @@ fn apply_pbr_lighting( // Spot lights (direct) for (var i: u32 = offset_and_counts[0] + offset_and_counts[1]; i < offset_and_counts[0] + offset_and_counts[1] + offset_and_counts[2]; i = i + 1u) { - let light_id = clustering::get_light_id(i); + let light_id = clustering::get_clusterable_object_id(i); var shadow: f32 = 1.0; if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal); } @@ -467,7 +467,7 @@ fn apply_pbr_lighting( // F0 = vec3(0.0) var transmitted_shadow: f32 = 1.0; if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT) - && (view_bindings::point_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { transmitted_shadow = shadows::fetch_spot_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal); } diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 727721acfcb64..0e88642333643 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -447,7 +447,7 @@ fn point_light(light_id: u32, input: ptr) -> vec3 let N = (*input).layers[LAYER_BASE].N; let V = (*input).V; - let light = &view_bindings::point_lights.data[light_id]; + let light = &view_bindings::clusterable_objects.data[light_id]; let light_to_frag = (*light).position_radius.xyz - P; let L = normalize(light_to_frag); let distance_square = dot(light_to_frag, light_to_frag); @@ -540,7 +540,7 @@ fn spot_light(light_id: u32, input: ptr) -> vec3 { // reuse the point light calculations let point_light = point_light(light_id, input); - let light = &view_bindings::point_lights.data[light_id]; + let light = &view_bindings::clusterable_objects.data[light_id]; // reconstruct spot dir from x/z and y-direction flag var spot_dir = vec3((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y); diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index c4a470fdf5316..cd4db0fb28afa 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -14,7 +14,7 @@ const flip_z: vec3 = vec3(1.0, 1.0, -1.0); fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = &view_bindings::point_lights.data[light_id]; + let light = &view_bindings::clusterable_objects.data[light_id]; // because the shadow maps align with the axes and the frustum planes are at 45 degrees // we can get the worldspace depth by taking the largest absolute axis @@ -47,7 +47,7 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v } fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = &view_bindings::point_lights.data[light_id]; + let light = &view_bindings::clusterable_objects.data[light_id]; let surface_to_light = (*light).position_radius.xyz - frag_position.xyz; diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index e37a096562d62..196bea143d8b0 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -7,7 +7,7 @@ use bevy::{ color::palettes::css::DEEP_PINK, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, math::{DVec2, DVec3}, - pbr::{ExtractedPointLight, GlobalLightMeta}, + pbr::{ExtractedPointLight, GlobalClusterableObjectMeta}, prelude::*, render::{camera::ScalingMode, Render, RenderApp, RenderSet}, window::{PresentMode, WindowResolution}, @@ -170,7 +170,7 @@ fn print_visible_light_count( time: Res