Rename "point light" to "clusterable object" in cluster contexts. (#13654)

We want to use the clustering infrastructure for light probes and decals
as well, not just point lights. This patch builds on top of #13640 and
performs the rename.

To make this series easier to review, this patch makes no code changes.
Only identifiers and comments are modified.

## Migration Guide

* In the PBR shaders, `point_lights` is now known as
`clusterable_objects`, `PointLight` is now known as `ClusterableObject`,
and `cluster_light_index_lists` is now known as
`clusterable_object_index_lists`.
This commit is contained in:
Patrick Walton 2024-06-04 04:01:13 -07:00 committed by GitHub
parent ab2add64fa
commit ad6872275f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 439 additions and 355 deletions

View file

@ -16,9 +16,9 @@ use bevy_transform::components::GlobalTransform;
use bevy_utils::tracing::warn; use bevy_utils::tracing::warn;
use crate::{ use crate::{
ClusterConfig, ClusterFarZMode, Clusters, GlobalVisiblePointLights, PointLight, SpotLight, ClusterConfig, ClusterFarZMode, Clusters, GlobalVisibleClusterableObjects, PointLight,
ViewClusterBindings, VisiblePointLights, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, SpotLight, ViewClusterBindings, VisibleClusterableObjects,
MAX_UNIFORM_BUFFER_POINT_LIGHTS, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS,
}; };
const NDC_MIN: Vec2 = Vec2::NEG_ONE; 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); const VEC2_HALF_NEGATIVE_Y: Vec2 = Vec2::new(0.5, -0.5);
#[derive(Clone)] #[derive(Clone)]
// data required for assigning lights to clusters // data required for assigning objects to clusters
pub(crate) struct PointLightAssignmentData { pub(crate) struct ClusterableObjectAssignmentData {
entity: Entity, entity: Entity,
transform: GlobalTransform, transform: GlobalTransform,
range: f32, range: f32,
@ -38,7 +38,7 @@ pub(crate) struct PointLightAssignmentData {
render_layers: RenderLayers, render_layers: RenderLayers,
} }
impl PointLightAssignmentData { impl ClusterableObjectAssignmentData {
pub fn sphere(&self) -> Sphere { pub fn sphere(&self) -> Sphere {
Sphere { Sphere {
center: self.transform.translation_vec3a(), center: self.transform.translation_vec3a(),
@ -49,9 +49,9 @@ impl PointLightAssignmentData {
// NOTE: Run this before update_point_light_frusta! // NOTE: Run this before update_point_light_frusta!
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn assign_lights_to_clusters( pub(crate) fn assign_objects_to_clusters(
mut commands: Commands, mut commands: Commands,
mut global_lights: ResMut<GlobalVisiblePointLights>, mut global_clusterable_objects: ResMut<GlobalVisibleClusterableObjects>,
mut views: Query<( mut views: Query<(
Entity, Entity,
&GlobalTransform, &GlobalTransform,
@ -60,7 +60,7 @@ pub(crate) fn assign_lights_to_clusters(
&ClusterConfig, &ClusterConfig,
&mut Clusters, &mut Clusters,
Option<&RenderLayers>, Option<&RenderLayers>,
Option<&mut VisiblePointLights>, Option<&mut VisibleClusterableObjects>,
)>, )>,
point_lights_query: Query<( point_lights_query: Query<(
Entity, Entity,
@ -76,25 +76,25 @@ pub(crate) fn assign_lights_to_clusters(
Option<&RenderLayers>, Option<&RenderLayers>,
&ViewVisibility, &ViewVisibility,
)>, )>,
mut lights: Local<Vec<PointLightAssignmentData>>, mut clusterable_objects: Local<Vec<ClusterableObjectAssignmentData>>,
mut cluster_aabb_spheres: Local<Vec<Option<Sphere>>>, mut cluster_aabb_spheres: Local<Vec<Option<Sphere>>>,
mut max_point_lights_warning_emitted: Local<bool>, mut max_clusterable_objects_warning_emitted: Local<bool>,
render_device: Option<Res<RenderDevice>>, render_device: Option<Res<RenderDevice>>,
) { ) {
let Some(render_device) = render_device else { let Some(render_device) = render_device else {
return; return;
}; };
global_lights.entities.clear(); global_clusterable_objects.entities.clear();
lights.clear(); clusterable_objects.clear();
// collect just the relevant light query data into a persisted vec to avoid reallocating each frame // collect just the relevant query data into a persisted vec to avoid reallocating each frame
lights.extend( clusterable_objects.extend(
point_lights_query point_lights_query
.iter() .iter()
.filter(|(.., visibility)| visibility.get()) .filter(|(.., visibility)| visibility.get())
.map( .map(
|(entity, transform, point_light, maybe_layers, _visibility)| { |(entity, transform, point_light, maybe_layers, _visibility)| {
PointLightAssignmentData { ClusterableObjectAssignmentData {
entity, entity,
transform: GlobalTransform::from_translation(transform.translation()), transform: GlobalTransform::from_translation(transform.translation()),
shadows_enabled: point_light.shadows_enabled, 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 spot_lights_query
.iter() .iter()
.filter(|(.., visibility)| visibility.get()) .filter(|(.., visibility)| visibility.get())
.map( .map(
|(entity, transform, spot_light, maybe_layers, _visibility)| { |(entity, transform, spot_light, maybe_layers, _visibility)| {
PointLightAssignmentData { ClusterableObjectAssignmentData {
entity, entity,
transform: *transform, transform: *transform,
shadows_enabled: spot_light.shadows_enabled, shadows_enabled: spot_light.shadows_enabled,
@ -129,55 +129,60 @@ pub(crate) fn assign_lights_to_clusters(
clustered_forward_buffer_binding_type, clustered_forward_buffer_binding_type,
BufferBindingType::Storage { .. } BufferBindingType::Storage { .. }
); );
if lights.len() > MAX_UNIFORM_BUFFER_POINT_LIGHTS && !supports_storage_buffers { if clusterable_objects.len() > MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS
lights.sort_by(|light_1, light_2| { && !supports_storage_buffers
crate::point_light_order( {
clusterable_objects.sort_by(|clusterable_object_1, clusterable_object_2| {
crate::clusterable_object_order(
( (
&light_1.entity, &clusterable_object_1.entity,
&light_1.shadows_enabled, &clusterable_object_1.shadows_enabled,
&light_1.spot_light_angle.is_some(), &clusterable_object_1.spot_light_angle.is_some(),
), ),
( (
&light_2.entity, &clusterable_object_2.entity,
&light_2.shadows_enabled, &clusterable_object_2.shadows_enabled,
&light_2.spot_light_angle.is_some(), &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 let frusta: Vec<_> = views
.iter() .iter()
.map(|(_, _, _, frustum, _, _, _, _)| *frustum) .map(|(_, _, _, frustum, _, _, _, _)| *frustum)
.collect(); .collect();
let mut lights_in_view_count = 0; let mut clusterable_objects_in_view_count = 0;
lights.retain(|light| { clusterable_objects.retain(|clusterable_object| {
// take one extra light to check if we should emit the warning // take one extra clusterable object to check if we should emit the warning
if lights_in_view_count == MAX_UNIFORM_BUFFER_POINT_LIGHTS + 1 { if clusterable_objects_in_view_count == MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + 1 {
false false
} else { } else {
let light_sphere = light.sphere(); let clusterable_object_sphere = clusterable_object.sphere();
let light_in_view = frusta let clusterable_object_in_view = frusta
.iter() .iter()
.any(|frustum| frustum.intersects_sphere(&light_sphere, true)); .any(|frustum| frustum.intersects_sphere(&clusterable_object_sphere, true));
if light_in_view { if clusterable_object_in_view {
lights_in_view_count += 1; 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!( warn!(
"MAX_UNIFORM_BUFFER_POINT_LIGHTS ({}) exceeded", "MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS ({}) exceeded",
MAX_UNIFORM_BUFFER_POINT_LIGHTS 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 ( for (
@ -188,15 +193,17 @@ pub(crate) fn assign_lights_to_clusters(
config, config,
clusters, clusters,
maybe_layers, maybe_layers,
mut visible_lights, mut visible_clusterable_objects,
) in &mut views ) in &mut views
{ {
let view_layers = maybe_layers.unwrap_or_default(); let view_layers = maybe_layers.unwrap_or_default();
let clusters = clusters.into_inner(); let clusters = clusters.into_inner();
if matches!(config, ClusterConfig::None) { if matches!(config, ClusterConfig::None) {
if visible_lights.is_some() { if visible_clusterable_objects.is_some() {
commands.entity(view_entity).remove::<VisiblePointLights>(); commands
.entity(view_entity)
.remove::<VisibleClusterableObjects>();
} }
clusters.clear(); clusters.clear();
continue; 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 is_orthographic = camera.clip_from_view().w_axis.w == 1.0;
let far_z = match config.far_z_mode() { let far_z = match config.far_z_mode() {
ClusterFarZMode::MaxLightRange => { ClusterFarZMode::MaxClusterableObjectRange => {
let view_from_world_row_2 = view_from_world.row(2); let view_from_world_row_2 = view_from_world.row(2);
lights clusterable_objects
.iter() .iter()
.map(|light| { .map(|object| {
-view_from_world_row_2.dot(light.transform.translation().extend(1.0)) -view_from_world_row_2.dot(object.transform.translation().extend(1.0))
+ light.range * view_from_world_scale.z + object.range * view_from_world_scale.z
}) })
.reduce(f32::max) .reduce(f32::max)
.unwrap_or(0.0) .unwrap_or(0.0)
@ -257,43 +264,44 @@ pub(crate) fn assign_lights_to_clusters(
if config.dynamic_resizing() { if config.dynamic_resizing() {
let mut cluster_index_estimate = 0.0; let mut cluster_index_estimate = 0.0;
for light in &lights { for clusterable_object in &clusterable_objects {
let light_sphere = light.sphere(); let clusterable_object_sphere = clusterable_object.sphere();
// Check if the light is within the view frustum // Check if the clusterable object is within the view frustum
if !frustum.intersects_sphere(&light_sphere, true) { if !frustum.intersects_sphere(&clusterable_object_sphere, true) {
continue; continue;
} }
// calculate a conservative aabb estimate of number of clusters affected by this light // 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 // 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 // it can overestimate more significantly when light ranges are only partially in view
let (light_aabb_min, light_aabb_max) = cluster_space_light_aabb( let (clusterable_object_aabb_min, clusterable_object_aabb_max) =
view_from_world, cluster_space_clusterable_object_aabb(
view_from_world_scale, view_from_world,
camera.clip_from_view(), view_from_world_scale,
&light_sphere, 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 // 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( let z_cluster_min = view_z_to_z_slice(
cluster_factors, cluster_factors,
requested_cluster_dimensions.z, requested_cluster_dimensions.z,
light_aabb_min.z, clusterable_object_aabb_min.z,
is_orthographic, is_orthographic,
); );
let z_cluster_max = view_z_to_z_slice( let z_cluster_max = view_z_to_z_slice(
cluster_factors, cluster_factors,
requested_cluster_dimensions.z, requested_cluster_dimensions.z,
light_aabb_max.z, clusterable_object_aabb_max.z,
is_orthographic, is_orthographic,
); );
let z_count = let z_count =
z_cluster_min.max(z_cluster_max) - z_cluster_min.min(z_cluster_max) + 1; 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 // 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_min = clusterable_object_aabb_min.xy();
let xy_max = light_aabb_max.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 // 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) let xy_count = (xy_max - xy_min)
* 0.5 * 0.5
@ -339,16 +347,16 @@ pub(crate) fn assign_lights_to_clusters(
let view_from_clip = camera.clip_from_view().inverse(); let view_from_clip = camera.clip_from_view().inverse();
for lights in &mut clusters.lights { for clusterable_objects in &mut clusters.clusterable_objects {
lights.entities.clear(); clusterable_objects.entities.clear();
lights.point_light_count = 0; clusterable_objects.point_light_count = 0;
lights.spot_light_count = 0; clusterable_objects.spot_light_count = 0;
} }
let cluster_count = let cluster_count =
(clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z) as usize; (clusters.dimensions.x * clusters.dimensions.y * clusters.dimensions.z) as usize;
clusters clusters
.lights .clusterable_objects
.resize_with(cluster_count, VisiblePointLights::default); .resize_with(cluster_count, VisibleClusterableObjects::default);
// initialize empty cluster bounding spheres // initialize empty cluster bounding spheres
cluster_aabb_spheres.clear(); cluster_aabb_spheres.clear();
@ -411,46 +419,53 @@ pub(crate) fn assign_lights_to_clusters(
z_planes.push(HalfSpace::new(normal.extend(d))); z_planes.push(HalfSpace::new(normal.extend(d)));
} }
let mut update_from_light_intersections = |visible_lights: &mut Vec<Entity>| { let mut update_from_object_intersections = |visible_clusterable_objects: &mut Vec<
for light in &lights { Entity,
// check if the light layers overlap the view layers >| {
if !view_layers.intersects(&light.render_layers) { 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; continue;
} }
let light_sphere = light.sphere(); let clusterable_object_sphere = clusterable_object.sphere();
// Check if the light is within the view frustum // Check if the clusterable object is within the view frustum
if !frustum.intersects_sphere(&light_sphere, true) { if !frustum.intersects_sphere(&clusterable_object_sphere, true) {
continue; continue;
} }
// NOTE: The light intersects the frustum so it must be visible and part of the global set // NOTE: The clusterable object intersects the frustum so it
global_lights.entities.insert(light.entity); // must be visible and part of the global set
visible_lights.push(light.entity); 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 // 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) = let (
cluster_space_light_aabb( clusterable_object_aabb_xy_ndc_z_view_min,
view_from_world, clusterable_object_aabb_xy_ndc_z_view_max,
view_from_world_scale, ) = cluster_space_clusterable_object_aabb(
camera.clip_from_view(), view_from_world,
&light_sphere, view_from_world_scale,
); camera.clip_from_view(),
&clusterable_object_sphere,
);
let min_cluster = ndc_position_to_cluster( let min_cluster = ndc_position_to_cluster(
clusters.dimensions, clusters.dimensions,
cluster_factors, cluster_factors,
is_orthographic, is_orthographic,
light_aabb_xy_ndc_z_view_min, clusterable_object_aabb_xy_ndc_z_view_min,
light_aabb_xy_ndc_z_view_min.z, clusterable_object_aabb_xy_ndc_z_view_min.z,
); );
let max_cluster = ndc_position_to_cluster( let max_cluster = ndc_position_to_cluster(
clusters.dimensions, clusters.dimensions,
cluster_factors, cluster_factors,
is_orthographic, is_orthographic,
light_aabb_xy_ndc_z_view_max, clusterable_object_aabb_xy_ndc_z_view_max,
light_aabb_xy_ndc_z_view_max.z, clusterable_object_aabb_xy_ndc_z_view_max.z,
); );
let (min_cluster, max_cluster) = let (min_cluster, max_cluster) =
(min_cluster.min(max_cluster), min_cluster.max(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 // 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 // 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! // center point on the axis of interest plus the radius, and that is not true!
let view_light_sphere = Sphere { let view_clusterable_object_sphere = Sphere {
center: Vec3A::from(view_from_world * light_sphere.center.extend(1.0)), center: Vec3A::from(
radius: light_sphere.radius * view_from_world_scale_max, 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(); 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() .truncate()
.normalize(), .normalize(),
angle_sin, angle_sin,
angle_cos, angle_cos,
) )
}); });
let light_center_clip = let clusterable_object_center_clip =
camera.clip_from_view() * view_light_sphere.center.extend(1.0); camera.clip_from_view() * view_clusterable_object_sphere.center.extend(1.0);
let light_center_ndc = light_center_clip.xyz() / light_center_clip.w; let object_center_ndc =
clusterable_object_center_clip.xyz() / clusterable_object_center_clip.w;
let cluster_coordinates = ndc_position_to_cluster( let cluster_coordinates = ndc_position_to_cluster(
clusters.dimensions, clusters.dimensions,
cluster_factors, cluster_factors,
is_orthographic, is_orthographic,
light_center_ndc, object_center_ndc,
view_light_sphere.center.z, 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) Some(cluster_coordinates.z)
} else { } else {
None None
}; };
let y_center = if light_center_ndc.y > 1.0 { let y_center = if object_center_ndc.y > 1.0 {
None None
} else if light_center_ndc.y < -1.0 { } else if object_center_ndc.y < -1.0 {
Some(clusters.dimensions.y + 1) Some(clusters.dimensions.y + 1)
} else { } else {
Some(cluster_coordinates.y) Some(cluster_coordinates.y)
}; };
for z in min_cluster.z..=max_cluster.z { 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() { if z_center.is_none() || z != z_center.unwrap() {
// The z plane closer to the light has the larger radius circle where the // The z plane closer to the clusterable object has the
// light sphere intersects the z plane. // larger radius circle where the light sphere
// intersects the z plane.
let z_plane = if z_center.is_some() && z < z_center.unwrap() { let z_plane = if z_center.is_some() && z < z_center.unwrap() {
z_planes[(z + 1) as usize] z_planes[(z + 1) as usize]
} else { } 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 // Project the sphere to this z plane and use its radius as the radius of a
// new, refined sphere. // new, refined sphere.
if let Some(projected) = project_to_plane_z(z_light, z_plane) { if let Some(projected) = project_to_plane_z(z_object, z_plane) {
z_light = projected; z_object = projected;
} else { } else {
continue; continue;
} }
} }
for y in min_cluster.y..=max_cluster.y { 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() { if y_center.is_none() || y != y_center.unwrap() {
// The y plane closer to the light has the larger radius circle where the // The y plane closer to the clusterable object has
// light sphere intersects the y plane. // the larger radius circle where the light sphere
// intersects the y plane.
let y_plane = if y_center.is_some() && y < y_center.unwrap() { let y_plane = if y_center.is_some() && y < y_center.unwrap() {
y_planes[(y + 1) as usize] y_planes[(y + 1) as usize]
} else { } 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 // Project the refined sphere to this y plane and use its radius as the
// radius of a new, even more refined sphere. // radius of a new, even more refined sphere.
if let Some(projected) = 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 { } else {
continue; continue;
} }
@ -542,9 +562,9 @@ pub(crate) fn assign_lights_to_clusters(
if min_x >= max_cluster.x if min_x >= max_cluster.x
|| -get_distance_x( || -get_distance_x(
x_planes[(min_x + 1) as usize], x_planes[(min_x + 1) as usize],
y_light.center, y_object.center,
is_orthographic, is_orthographic,
) + y_light.radius ) + y_object.radius
> 0.0 > 0.0
{ {
break; break;
@ -557,9 +577,9 @@ pub(crate) fn assign_lights_to_clusters(
if max_x <= min_x if max_x <= min_x
|| get_distance_x( || get_distance_x(
x_planes[max_x as usize], x_planes[max_x as usize],
y_light.center, y_object.center,
is_orthographic, is_orthographic,
) + y_light.radius ) + y_object.radius
> 0.0 > 0.0
{ {
break; break;
@ -601,7 +621,8 @@ pub(crate) fn assign_lights_to_clusters(
// test -- based on https://bartwronski.com/2017/04/13/cull-that-cone/ // test -- based on https://bartwronski.com/2017/04/13/cull-that-cone/
let spot_light_offset = Vec3::from( 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 spot_light_dist_sq = spot_light_offset.length_squared();
let v1_len = spot_light_offset.dot(view_light_direction); 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 let front_cull = v1_len
> cluster_aabb_sphere.radius > 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; let back_cull = v1_len < -cluster_aabb_sphere.radius;
if !angle_cull && !front_cull && !back_cull { if !angle_cull && !front_cull && !back_cull {
// this cluster is affected by the spot light // this cluster is affected by the spot light
clusters.lights[cluster_index].entities.push(light.entity); clusters.clusterable_objects[cluster_index]
clusters.lights[cluster_index].spot_light_count += 1; .entities
.push(clusterable_object.entity);
clusters.clusterable_objects[cluster_index].spot_light_count +=
1;
} }
cluster_index += clusters.dimensions.z as usize; cluster_index += clusters.dimensions.z as usize;
} }
} else { } else {
for _ in min_x..=max_x { for _ in min_x..=max_x {
// all clusters within range are affected by point lights // all clusters within range are affected by point lights
clusters.lights[cluster_index].entities.push(light.entity); clusters.clusterable_objects[cluster_index]
clusters.lights[cluster_index].point_light_count += 1; .entities
.push(clusterable_object.entity);
clusters.clusterable_objects[cluster_index].point_light_count += 1;
cluster_index += clusters.dimensions.z as usize; 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 // reuse existing visible clusterable objects Vec, if it exists
if let Some(visible_lights) = visible_lights.as_mut() { if let Some(visible_clusterable_objects) = visible_clusterable_objects.as_mut() {
visible_lights.entities.clear(); visible_clusterable_objects.entities.clear();
update_from_light_intersections(&mut visible_lights.entities); update_from_object_intersections(&mut visible_clusterable_objects.entities);
} else { } else {
let mut entities = Vec::new(); let mut entities = Vec::new();
update_from_light_intersections(&mut entities); update_from_object_intersections(&mut entities);
commands.entity(view_entity).insert(VisiblePointLights { commands
entities, .entity(view_entity)
..Default::default() .insert(VisibleClusterableObjects {
}); entities,
..Default::default()
});
} }
} }
} }
@ -759,22 +787,24 @@ fn ndc_position_to_cluster(
.clamp(UVec3::ZERO, cluster_dimensions - UVec3::ONE) .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 /// Returns a `(Vec3, Vec3)` containing minimum and maximum with
/// `X` and `Y` in normalized device coordinates with range `[-1, 1]` /// `X` and `Y` in normalized device coordinates with range `[-1, 1]`
/// `Z` in view space, with range `[-inf, -f32::MIN_POSITIVE]` /// `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: Mat4,
view_from_world_scale: Vec3, view_from_world_scale: Vec3,
clip_from_view: Mat4, clip_from_view: Mat4,
light_sphere: &Sphere, clusterable_object_sphere: &Sphere,
) -> (Vec3, Vec3) { ) -> (Vec3, Vec3) {
let light_aabb_view = Aabb { let clusterable_object_aabb_view = Aabb {
center: Vec3A::from(view_from_world * light_sphere.center.extend(1.0)), center: Vec3A::from(view_from_world * clusterable_object_sphere.center.extend(1.0)),
half_extents: Vec3A::from(light_sphere.radius * view_from_world_scale.abs()), half_extents: Vec3A::from(clusterable_object_sphere.radius * view_from_world_scale.abs()),
}; };
let (mut light_aabb_view_min, mut light_aabb_view_max) = let (mut clusterable_object_aabb_view_min, mut clusterable_object_aabb_view_max) = (
(light_aabb_view.min(), light_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 // 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. // 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 // 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 // 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. // 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); clusterable_object_aabb_view_min.z = clusterable_object_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_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 // 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 // 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 // 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. // the min and max of those projected points addresses this.
let ( let (
light_aabb_view_xymin_near, clusterable_object_aabb_view_xymin_near,
light_aabb_view_xymin_far, clusterable_object_aabb_view_xymin_far,
light_aabb_view_xymax_near, clusterable_object_aabb_view_xymax_near,
light_aabb_view_xymax_far, clusterable_object_aabb_view_xymax_far,
) = ( ) = (
light_aabb_view_min, clusterable_object_aabb_view_min,
light_aabb_view_min.xy().extend(light_aabb_view_max.z), clusterable_object_aabb_view_min
light_aabb_view_max.xy().extend(light_aabb_view_min.z), .xy()
light_aabb_view_max, .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 ( let (
light_aabb_clip_xymin_near, clusterable_object_aabb_clip_xymin_near,
light_aabb_clip_xymin_far, clusterable_object_aabb_clip_xymin_far,
light_aabb_clip_xymax_near, clusterable_object_aabb_clip_xymax_near,
light_aabb_clip_xymax_far, clusterable_object_aabb_clip_xymax_far,
) = ( ) = (
clip_from_view * light_aabb_view_xymin_near.extend(1.0), clip_from_view * clusterable_object_aabb_view_xymin_near.extend(1.0),
clip_from_view * light_aabb_view_xymin_far.extend(1.0), clip_from_view * clusterable_object_aabb_view_xymin_far.extend(1.0),
clip_from_view * light_aabb_view_xymax_near.extend(1.0), clip_from_view * clusterable_object_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_xymax_far.extend(1.0),
); );
let ( let (
light_aabb_ndc_xymin_near, clusterable_object_aabb_ndc_xymin_near,
light_aabb_ndc_xymin_far, clusterable_object_aabb_ndc_xymin_far,
light_aabb_ndc_xymax_near, clusterable_object_aabb_ndc_xymax_near,
light_aabb_ndc_xymax_far, clusterable_object_aabb_ndc_xymax_far,
) = ( ) = (
light_aabb_clip_xymin_near.xyz() / light_aabb_clip_xymin_near.w, clusterable_object_aabb_clip_xymin_near.xyz() / clusterable_object_aabb_clip_xymin_near.w,
light_aabb_clip_xymin_far.xyz() / light_aabb_clip_xymin_far.w, clusterable_object_aabb_clip_xymin_far.xyz() / clusterable_object_aabb_clip_xymin_far.w,
light_aabb_clip_xymax_near.xyz() / light_aabb_clip_xymax_near.w, clusterable_object_aabb_clip_xymax_near.xyz() / clusterable_object_aabb_clip_xymax_near.w,
light_aabb_clip_xymax_far.xyz() / light_aabb_clip_xymax_far.w, clusterable_object_aabb_clip_xymax_far.xyz() / clusterable_object_aabb_clip_xymax_far.w,
); );
let (light_aabb_ndc_min, light_aabb_ndc_max) = ( let (clusterable_object_aabb_ndc_min, clusterable_object_aabb_ndc_max) = (
light_aabb_ndc_xymin_near clusterable_object_aabb_ndc_xymin_near
.min(light_aabb_ndc_xymin_far) .min(clusterable_object_aabb_ndc_xymin_far)
.min(light_aabb_ndc_xymax_near) .min(clusterable_object_aabb_ndc_xymax_near)
.min(light_aabb_ndc_xymax_far), .min(clusterable_object_aabb_ndc_xymax_far),
light_aabb_ndc_xymin_near clusterable_object_aabb_ndc_xymin_near
.max(light_aabb_ndc_xymin_far) .max(clusterable_object_aabb_ndc_xymin_far)
.max(light_aabb_ndc_xymax_near) .max(clusterable_object_aabb_ndc_xymax_near)
.max(light_aabb_ndc_xymax_far), .max(clusterable_object_aabb_ndc_xymax_far),
); );
// clamp to ndc coords without depth // clamp to ndc coords without depth
let (aabb_min_ndc, aabb_max_ndc) = ( let (aabb_min_ndc, aabb_max_ndc) = (
light_aabb_ndc_min.xy().clamp(NDC_MIN, NDC_MAX), clusterable_object_aabb_ndc_min.xy().clamp(NDC_MIN, NDC_MAX),
light_aabb_ndc_max.xy().clamp(NDC_MIN, NDC_MAX), clusterable_object_aabb_ndc_max.xy().clamp(NDC_MIN, NDC_MAX),
); );
// pack unadjusted z depth into the vecs // pack unadjusted z depth into the vecs
( (
aabb_min_ndc.extend(light_aabb_view_min.z), aabb_min_ndc.extend(clusterable_object_aabb_view_min.z),
aabb_max_ndc.extend(light_aabb_view_max.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 // 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<Sphere> { fn project_to_plane_z(z_object: Sphere, z_plane: HalfSpace) -> Option<Sphere> {
// p = sphere center // p = sphere center
// n = plane normal // n = plane normal
// d = n.p if p is in the plane // 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<Sphere> {
// = pz * nz // = pz * nz
// => pz = d / nz // => pz = d / nz
let z = z_plane.d() / z_plane.normal_d().z; let z = z_plane.d() / z_plane.normal_d().z;
let distance_to_plane = z - z_light.center.z; let distance_to_plane = z - z_object.center.z;
if distance_to_plane.abs() > z_light.radius { if distance_to_plane.abs() > z_object.radius {
return None; return None;
} }
Some(Sphere { Some(Sphere {
center: Vec3A::from(z_light.center.xy().extend(z)), center: Vec3A::from(z_object.center.xy().extend(z)),
// hypotenuse length = radius // hypotenuse length = radius
// pythagoras = (distance to plane)^2 + b^2 = radius^2 // 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 // NOTE: This exploits the fact that a y-plane normal has only y and z components
fn project_to_plane_y( fn project_to_plane_y(
y_light: Sphere, y_object: Sphere,
y_plane: HalfSpace, y_plane: HalfSpace,
is_orthographic: bool, is_orthographic: bool,
) -> Option<Sphere> { ) -> Option<Sphere> {
let distance_to_plane = if is_orthographic { let distance_to_plane = if is_orthographic {
y_plane.d() - y_light.center.y y_plane.d() - y_object.center.y
} else { } 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; return None;
} }
Some(Sphere { Some(Sphere {
center: y_light.center + distance_to_plane * y_plane.normal(), center: y_object.center + distance_to_plane * y_plane.normal(),
radius: (y_light.radius * y_light.radius - distance_to_plane * distance_to_plane).sqrt(), radius: (y_object.radius * y_object.radius - distance_to_plane * distance_to_plane).sqrt(),
}) })
} }

View file

@ -23,7 +23,7 @@ use bevy_render::{
}; };
use bevy_utils::{hashbrown::HashSet, tracing::warn}; 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; use crate::MeshPipeline;
mod assign; mod assign;
@ -32,14 +32,14 @@ mod assign;
mod test; mod test;
// NOTE: this must be kept in sync with the same constants in pbr.frag // 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 // 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() // at least that many are supported using this constant and SupportedBindingType::from_device()
pub const CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT: u32 = 3; pub const CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT: u32 = 3;
// this must match CLUSTER_COUNT_SIZE in pbr.wgsl // 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_COUNT_SIZE: u32 = 9;
const CLUSTER_OFFSET_MASK: u32 = (1 << (32 - (CLUSTER_COUNT_SIZE * 2))) - 1; 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 /// rendering
#[derive(Debug, Copy, Clone, Reflect)] #[derive(Debug, Copy, Clone, Reflect)]
pub enum ClusterFarZMode { pub enum ClusterFarZMode {
/// Calculate the required maximum z-depth based on currently visible lights. /// Calculate the required maximum z-depth based on currently visible
/// Makes better use of available clusters, speeding up GPU lighting operations /// clusterable objects. Makes better use of available clusters, speeding
/// at the expense of some CPU time and using more indices in the cluster light /// up GPU lighting operations at the expense of some CPU time and using
/// index lists. /// more indices in the clusterable object index lists.
MaxLightRange, MaxClusterableObjectRange,
/// Constant max z-depth /// Constant max z-depth
Constant(f32), Constant(f32),
} }
@ -81,7 +81,7 @@ pub struct ClusterZConfig {
#[derive(Debug, Copy, Clone, Component, Reflect)] #[derive(Debug, Copy, Clone, Component, Reflect)]
#[reflect(Component)] #[reflect(Component)]
pub enum ClusterConfig { pub enum ClusterConfig {
/// Disable light cluster calculations for this view /// Disable cluster calculations for this view
None, None,
/// One single cluster. Optimal for low-light complexity scenes or scenes where /// One single cluster. Optimal for low-light complexity scenes or scenes where
/// most lights affect the entire scene. /// most lights affect the entire scene.
@ -91,7 +91,7 @@ pub enum ClusterConfig {
dimensions: UVec3, dimensions: UVec3,
z_config: ClusterZConfig, z_config: ClusterZConfig,
/// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding /// 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, dynamic_resizing: bool,
}, },
/// Fixed number of `Z` slices, `X` and `Y` calculated to give square clusters /// Fixed number of `Z` slices, `X` and `Y` calculated to give square clusters
@ -104,7 +104,7 @@ pub enum ClusterConfig {
z_slices: u32, z_slices: u32,
z_config: ClusterZConfig, z_config: ClusterZConfig,
/// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding /// 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, dynamic_resizing: bool,
}, },
} }
@ -119,29 +119,29 @@ pub struct Clusters {
/// and explicitly-configured to avoid having unnecessarily many slices close to the camera. /// and explicitly-configured to avoid having unnecessarily many slices close to the camera.
pub(crate) near: f32, pub(crate) near: f32,
pub(crate) far: f32, pub(crate) far: f32,
pub(crate) lights: Vec<VisiblePointLights>, pub(crate) clusterable_objects: Vec<VisibleClusterableObjects>,
} }
#[derive(Clone, Component, Debug, Default)] #[derive(Clone, Component, Debug, Default)]
pub struct VisiblePointLights { pub struct VisibleClusterableObjects {
pub(crate) entities: Vec<Entity>, pub(crate) entities: Vec<Entity>,
pub point_light_count: usize, pub point_light_count: usize,
pub spot_light_count: usize, pub spot_light_count: usize,
} }
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct GlobalVisiblePointLights { pub struct GlobalVisibleClusterableObjects {
pub(crate) entities: HashSet<Entity>, pub(crate) entities: HashSet<Entity>,
} }
#[derive(Resource)] #[derive(Resource)]
pub struct GlobalLightMeta { pub struct GlobalClusterableObjectMeta {
pub gpu_point_lights: GpuPointLights, pub gpu_clusterable_objects: GpuClusterableObjects,
pub entity_to_index: EntityHashMap<usize>, pub entity_to_index: EntityHashMap<usize>,
} }
#[derive(Copy, Clone, ShaderType, Default, Debug)] #[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 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 // For spot lights: 2 components of the direction (x,z), spot_scale and spot_offset
pub(crate) light_custom_data: Vec4, pub(crate) light_custom_data: Vec4,
@ -153,20 +153,20 @@ pub struct GpuPointLight {
pub(crate) spot_light_tan_angle: f32, pub(crate) spot_light_tan_angle: f32,
} }
pub enum GpuPointLights { pub enum GpuClusterableObjects {
Uniform(UniformBuffer<GpuPointLightsUniform>), Uniform(UniformBuffer<GpuClusterableObjectsUniform>),
Storage(StorageBuffer<GpuPointLightsStorage>), Storage(StorageBuffer<GpuClusterableObjectsStorage>),
} }
#[derive(ShaderType)] #[derive(ShaderType)]
pub struct GpuPointLightsUniform { pub struct GpuClusterableObjectsUniform {
data: Box<[GpuPointLight; MAX_UNIFORM_BUFFER_POINT_LIGHTS]>, data: Box<[GpuClusterableObject; MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS]>,
} }
#[derive(ShaderType, Default)] #[derive(ShaderType, Default)]
pub struct GpuPointLightsStorage { pub struct GpuClusterableObjectsStorage {
#[size(runtime)] #[size(runtime)]
data: Vec<GpuPointLight>, data: Vec<GpuClusterableObject>,
} }
#[derive(Component)] #[derive(Component)]
@ -178,14 +178,14 @@ pub struct ExtractedClusterConfig {
pub(crate) dimensions: UVec3, pub(crate) dimensions: UVec3,
} }
enum ExtractedClustersPointLightsElement { enum ExtractedClusterableObjectElement {
ClusterHeader(u32, u32), ClusterHeader(u32, u32),
LightEntity(Entity), ClusterableObjectEntity(Entity),
} }
#[derive(Component)] #[derive(Component)]
pub struct ExtractedClustersPointLights { pub struct ExtractedClusterableObjects {
data: Vec<ExtractedClustersPointLightsElement>, data: Vec<ExtractedClusterableObjectElement>,
} }
#[derive(ShaderType)] #[derive(ShaderType)]
@ -194,7 +194,7 @@ struct GpuClusterOffsetsAndCountsUniform {
} }
#[derive(ShaderType, Default)] #[derive(ShaderType, Default)]
struct GpuClusterLightIndexListsStorage { struct GpuClusterableObjectIndexListsStorage {
#[size(runtime)] #[size(runtime)]
data: Vec<u32>, data: Vec<u32>,
} }
@ -208,12 +208,12 @@ struct GpuClusterOffsetsAndCountsStorage {
enum ViewClusterBuffers { enum ViewClusterBuffers {
Uniform { Uniform {
// NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
cluster_light_index_lists: UniformBuffer<GpuClusterLightIndexListsUniform>, clusterable_object_index_lists: UniformBuffer<GpuClusterableObjectIndexListsUniform>,
// NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>, cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>,
}, },
Storage { Storage {
cluster_light_index_lists: StorageBuffer<GpuClusterLightIndexListsStorage>, clusterable_object_index_lists: StorageBuffer<GpuClusterableObjectIndexListsStorage>,
cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>, cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>,
}, },
} }
@ -229,7 +229,7 @@ impl Default for ClusterZConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
first_slice_depth: 5.0, 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 { fn far_z_mode(&self) -> ClusterFarZMode {
match self { match self {
ClusterConfig::None => ClusterFarZMode::Constant(0.0), ClusterConfig::None => ClusterFarZMode::Constant(0.0),
ClusterConfig::Single => ClusterFarZMode::MaxLightRange, ClusterConfig::Single => ClusterFarZMode::MaxClusterableObjectRange,
ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => { ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
z_config.far_z_mode z_config.far_z_mode
} }
@ -342,7 +342,7 @@ impl Clusters {
self.dimensions = UVec3::ZERO; self.dimensions = UVec3::ZERO;
self.near = 0.0; self.near = 0.0;
self.far = 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(); 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 commands
.entity(entity) .entity(entity)
.insert((Clusters::default(), config)); .insert((Clusters::default(), config));
} }
} }
impl VisiblePointLights { impl VisibleClusterableObjects {
#[inline] #[inline]
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> { pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
self.entities.iter() self.entities.iter()
@ -380,7 +381,7 @@ impl VisiblePointLights {
} }
} }
impl GlobalVisiblePointLights { impl GlobalVisibleClusterableObjects {
#[inline] #[inline]
pub fn iter(&self) -> impl Iterator<Item = &Entity> { pub fn iter(&self) -> impl Iterator<Item = &Entity> {
self.entities.iter() self.entities.iter()
@ -392,7 +393,7 @@ impl GlobalVisiblePointLights {
} }
} }
impl FromWorld for GlobalLightMeta { impl FromWorld for GlobalClusterableObjectMeta {
fn from_world(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
Self::new( Self::new(
world world
@ -402,16 +403,16 @@ impl FromWorld for GlobalLightMeta {
} }
} }
impl GlobalLightMeta { impl GlobalClusterableObjectMeta {
pub fn new(buffer_binding_type: BufferBindingType) -> Self { pub fn new(buffer_binding_type: BufferBindingType) -> Self {
Self { Self {
gpu_point_lights: GpuPointLights::new(buffer_binding_type), gpu_clusterable_objects: GpuClusterableObjects::new(buffer_binding_type),
entity_to_index: EntityHashMap::default(), entity_to_index: EntityHashMap::default(),
} }
} }
} }
impl GpuPointLights { impl GpuClusterableObjects {
fn new(buffer_binding_type: BufferBindingType) -> Self { fn new(buffer_binding_type: BufferBindingType) -> Self {
match buffer_binding_type { match buffer_binding_type {
BufferBindingType::Storage { .. } => Self::storage(), BufferBindingType::Storage { .. } => Self::storage(),
@ -427,17 +428,19 @@ impl GpuPointLights {
Self::Storage(StorageBuffer::default()) Self::Storage(StorageBuffer::default())
} }
pub(crate) fn set(&mut self, mut lights: Vec<GpuPointLight>) { pub(crate) fn set(&mut self, mut clusterable_objects: Vec<GpuClusterableObject>) {
match self { match self {
GpuPointLights::Uniform(buffer) => { GpuClusterableObjects::Uniform(buffer) => {
let len = lights.len().min(MAX_UNIFORM_BUFFER_POINT_LIGHTS); let len = clusterable_objects
let src = &lights[..len]; .len()
.min(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS);
let src = &clusterable_objects[..len];
let dst = &mut buffer.get_mut().data[..len]; let dst = &mut buffer.get_mut().data[..len];
dst.copy_from_slice(src); dst.copy_from_slice(src);
} }
GpuPointLights::Storage(buffer) => { GpuClusterableObjects::Storage(buffer) => {
buffer.get_mut().data.clear(); 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, render_queue: &RenderQueue,
) { ) {
match self { match self {
GpuPointLights::Uniform(buffer) => buffer.write_buffer(render_device, render_queue), GpuClusterableObjects::Uniform(buffer) => {
GpuPointLights::Storage(buffer) => buffer.write_buffer(render_device, render_queue), buffer.write_buffer(render_device, render_queue);
}
GpuClusterableObjects::Storage(buffer) => {
buffer.write_buffer(render_device, render_queue);
}
} }
} }
pub fn binding(&self) -> Option<BindingResource> { pub fn binding(&self) -> Option<BindingResource> {
match self { match self {
GpuPointLights::Uniform(buffer) => buffer.binding(), GpuClusterableObjects::Uniform(buffer) => buffer.binding(),
GpuPointLights::Storage(buffer) => buffer.binding(), GpuClusterableObjects::Storage(buffer) => buffer.binding(),
} }
} }
pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZeroU64 { pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZeroU64 {
match buffer_binding_type { match buffer_binding_type {
BufferBindingType::Storage { .. } => GpuPointLightsStorage::min_size(), BufferBindingType::Storage { .. } => GpuClusterableObjectsStorage::min_size(),
BufferBindingType::Uniform => GpuPointLightsUniform::min_size(), BufferBindingType::Uniform => GpuClusterableObjectsUniform::min_size(),
} }
} }
} }
impl Default for GpuPointLightsUniform { impl Default for GpuClusterableObjectsUniform {
fn default() -> Self { fn default() -> Self {
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)] #[allow(clippy::too_many_arguments)]
// Sort lights by // 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 vs spot-light, so that we can iterate point lights and spot
// point light shadows and `spot_light_shadow_maps_count` spot light shadow maps, // lights in contiguous blocks in the fragment shader,
// - 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( // * 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_1, shadows_enabled_1, is_spot_light_1): (&Entity, &bool, &bool),
(entity_2, shadows_enabled_2, is_spot_light_2): (&Entity, &bool, &bool), (entity_2, shadows_enabled_2, is_spot_light_2): (&Entity, &bool, &bool),
) -> std::cmp::Ordering { ) -> std::cmp::Ordering {
@ -502,20 +518,26 @@ pub fn extract_clusters(
continue; continue;
} }
let num_entities: usize = clusters.lights.iter().map(|l| l.entities.len()).sum(); let num_entities: usize = clusters
let mut data = Vec::with_capacity(clusters.lights.len() + num_entities); .clusterable_objects
for cluster_lights in &clusters.lights { .iter()
data.push(ExtractedClustersPointLightsElement::ClusterHeader( .map(|l| l.entities.len())
cluster_lights.point_light_count as u32, .sum();
cluster_lights.spot_light_count as u32, 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 { for clusterable_entity in &cluster_objects.entities {
data.push(ExtractedClustersPointLightsElement::LightEntity(*l)); data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
*clusterable_entity,
));
} }
} }
commands.get_or_spawn(entity).insert(( commands.get_or_spawn(entity).insert((
ExtractedClustersPointLights { data }, ExtractedClusterableObjects { data },
ExtractedClusterConfig { ExtractedClusterConfig {
near: clusters.near, near: clusters.near,
far: clusters.far, far: clusters.far,
@ -530,8 +552,8 @@ pub fn prepare_clusters(
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>, render_queue: Res<RenderQueue>,
mesh_pipeline: Res<MeshPipeline>, mesh_pipeline: Res<MeshPipeline>,
global_light_meta: Res<GlobalLightMeta>, global_clusterable_object_meta: Res<GlobalClusterableObjectMeta>,
views: Query<(Entity, &ExtractedClustersPointLights)>, views: Query<(Entity, &ExtractedClusterableObjects)>,
) { ) {
let render_device = render_device.into_inner(); let render_device = render_device.into_inner();
let supports_storage_buffers = matches!( let supports_storage_buffers = matches!(
@ -545,7 +567,7 @@ pub fn prepare_clusters(
for record in &extracted_clusters.data { for record in &extracted_clusters.data {
match record { match record {
ExtractedClustersPointLightsElement::ClusterHeader( ExtractedClusterableObjectElement::ClusterHeader(
point_light_count, point_light_count,
spot_light_count, spot_light_count,
) => { ) => {
@ -556,15 +578,20 @@ pub fn prepare_clusters(
*spot_light_count as usize, *spot_light_count as usize,
); );
} }
ExtractedClustersPointLightsElement::LightEntity(entity) => { ExtractedClusterableObjectElement::ClusterableObjectEntity(entity) => {
if let Some(light_index) = global_light_meta.entity_to_index.get(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 if view_clusters_bindings.n_indices() >= ViewClusterBindings::MAX_INDICES
&& !supports_storage_buffers && !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; 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) { pub fn clear(&mut self) {
match &mut self.buffers { match &mut self.buffers {
ViewClusterBuffers::Uniform { ViewClusterBuffers::Uniform {
cluster_light_index_lists, clusterable_object_index_lists,
cluster_offsets_and_counts, 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]; *cluster_offsets_and_counts.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS];
} }
ViewClusterBuffers::Storage { ViewClusterBuffers::Storage {
cluster_light_index_lists, clusterable_object_index_lists,
cluster_offsets_and_counts, 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(); cluster_offsets_and_counts.get_mut().data.clear();
} }
} }
@ -648,7 +676,7 @@ impl ViewClusterBindings {
pub fn push_index(&mut self, index: usize) { pub fn push_index(&mut self, index: usize) {
match &mut self.buffers { match &mut self.buffers {
ViewClusterBuffers::Uniform { ViewClusterBuffers::Uniform {
cluster_light_index_lists, clusterable_object_index_lists,
.. ..
} => { } => {
let array_index = self.n_indices >> 4; // >> 4 is equivalent to / 16 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 sub_index = self.n_indices & ((1 << 2) - 1);
let index = index as u32; 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); index << (8 * sub_index);
} }
ViewClusterBuffers::Storage { 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) { pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
match &mut self.buffers { match &mut self.buffers {
ViewClusterBuffers::Uniform { ViewClusterBuffers::Uniform {
cluster_light_index_lists, clusterable_object_index_lists,
cluster_offsets_and_counts, 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); cluster_offsets_and_counts.write_buffer(render_device, render_queue);
} }
ViewClusterBuffers::Storage { ViewClusterBuffers::Storage {
cluster_light_index_lists, clusterable_object_index_lists,
cluster_offsets_and_counts, 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); cluster_offsets_and_counts.write_buffer(render_device, render_queue);
} }
} }
} }
pub fn light_index_lists_binding(&self) -> Option<BindingResource> { pub fn clusterable_object_index_lists_binding(&self) -> Option<BindingResource> {
match &self.buffers { match &self.buffers {
ViewClusterBuffers::Uniform { ViewClusterBuffers::Uniform {
cluster_light_index_lists, clusterable_object_index_lists,
.. ..
} => cluster_light_index_lists.binding(), } => clusterable_object_index_lists.binding(),
ViewClusterBuffers::Storage { 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, buffer_binding_type: BufferBindingType,
) -> NonZeroU64 { ) -> NonZeroU64 {
match buffer_binding_type { match buffer_binding_type {
BufferBindingType::Storage { .. } => GpuClusterLightIndexListsStorage::min_size(), BufferBindingType::Storage { .. } => GpuClusterableObjectIndexListsStorage::min_size(),
BufferBindingType::Uniform => GpuClusterLightIndexListsUniform::min_size(), BufferBindingType::Uniform => GpuClusterableObjectIndexListsUniform::min_size(),
} }
} }
@ -744,21 +775,21 @@ impl ViewClusterBuffers {
fn uniform() -> Self { fn uniform() -> Self {
ViewClusterBuffers::Uniform { ViewClusterBuffers::Uniform {
cluster_light_index_lists: UniformBuffer::default(), clusterable_object_index_lists: UniformBuffer::default(),
cluster_offsets_and_counts: UniformBuffer::default(), cluster_offsets_and_counts: UniformBuffer::default(),
} }
} }
fn storage() -> Self { fn storage() -> Self {
ViewClusterBuffers::Storage { ViewClusterBuffers::Storage {
cluster_light_index_lists: StorageBuffer::default(), clusterable_object_index_lists: StorageBuffer::default(),
cluster_offsets_and_counts: StorageBuffer::default(), cluster_offsets_and_counts: StorageBuffer::default(),
} }
} }
} }
// NOTE: With uniform buffer max binding size as 16384 bytes // 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 // buffer, which means the count can be at most 256 so it
// needs 9 bits. // needs 9 bits.
// The array of indices can also use u8 and that means the // 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)] #[derive(ShaderType)]
struct GpuClusterLightIndexListsUniform { struct GpuClusterableObjectIndexListsUniform {
data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>, 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 // 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 { fn default() -> Self {
Self { Self {
data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]), data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]),

View file

@ -298,7 +298,7 @@ impl Plugin for PbrPlugin {
.register_type::<FogSettings>() .register_type::<FogSettings>()
.register_type::<ShadowFilteringMethod>() .register_type::<ShadowFilteringMethod>()
.init_resource::<AmbientLight>() .init_resource::<AmbientLight>()
.init_resource::<GlobalVisiblePointLights>() .init_resource::<GlobalVisibleClusterableObjects>()
.init_resource::<DirectionalLightShadowMap>() .init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>() .init_resource::<PointLightShadowMap>()
.register_type::<DefaultOpaqueRendererMethod>() .register_type::<DefaultOpaqueRendererMethod>()
@ -339,7 +339,7 @@ impl Plugin for PbrPlugin {
PostUpdate, PostUpdate,
( (
add_clusters.in_set(SimulationLightSystems::AddClusters), add_clusters.in_set(SimulationLightSystems::AddClusters),
crate::assign_lights_to_clusters crate::assign_objects_to_clusters
.in_set(SimulationLightSystems::AssignLightsToClusters) .in_set(SimulationLightSystems::AssignLightsToClusters)
.after(TransformSystem::TransformPropagate) .after(TransformSystem::TransformPropagate)
.after(VisibilitySystems::CheckVisibility) .after(VisibilitySystems::CheckVisibility)
@ -427,7 +427,7 @@ impl Plugin for PbrPlugin {
// Extract the required data from the main world // Extract the required data from the main world
render_app render_app
.init_resource::<ShadowSamplers>() .init_resource::<ShadowSamplers>()
.init_resource::<GlobalLightMeta>(); .init_resource::<GlobalClusterableObjectMeta>();
} }
} }

View file

@ -561,7 +561,7 @@ pub fn update_directional_light_frusta(
// NOTE: Run this after assign_lights_to_clusters! // NOTE: Run this after assign_lights_to_clusters!
pub fn update_point_light_frusta( pub fn update_point_light_frusta(
global_lights: Res<GlobalVisiblePointLights>, global_lights: Res<GlobalVisibleClusterableObjects>,
mut views: Query< mut views: Query<
(Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta), (Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta),
Or<(Changed<GlobalTransform>, Changed<PointLight>)>, Or<(Changed<GlobalTransform>, Changed<PointLight>)>,
@ -605,7 +605,7 @@ pub fn update_point_light_frusta(
} }
pub fn update_spot_light_frusta( pub fn update_spot_light_frusta(
global_lights: Res<GlobalVisiblePointLights>, global_lights: Res<GlobalVisibleClusterableObjects>,
mut views: Query< mut views: Query<
(Entity, &GlobalTransform, &SpotLight, &mut Frustum), (Entity, &GlobalTransform, &SpotLight, &mut Frustum),
Or<(Changed<GlobalTransform>, Changed<SpotLight>)>, Or<(Changed<GlobalTransform>, Changed<SpotLight>)>,
@ -639,7 +639,7 @@ pub fn update_spot_light_frusta(
} }
pub fn check_light_mesh_visibility( pub fn check_light_mesh_visibility(
visible_point_lights: Query<&VisiblePointLights>, visible_point_lights: Query<&VisibleClusterableObjects>,
mut point_lights: Query<( mut point_lights: Query<(
&PointLight, &PointLight,
&GlobalTransform, &GlobalTransform,

View file

@ -53,13 +53,14 @@ fn unpack_offset_and_counts(cluster_index: u32) -> vec3<u32> {
#endif #endif
} }
fn get_light_id(index: u32) -> u32 { fn get_clusterable_object_id(index: u32) -> u32 {
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
return bindings::cluster_light_index_lists.data[index]; return bindings::clusterable_object_index_lists.data[index];
#else #else
// The index is correct but in cluster_light_index_lists we pack 4 u8s into a u32 // The index is correct but in clusterable_object_index_lists we pack 4 u8s into a u32
// This means the index into cluster_light_index_lists is index / 4 // This means the index into clusterable_object_index_lists is index / 4
let indices = bindings::cluster_light_index_lists.data[index >> 4u][(index >> 2u) & ((1u << 2u) - 1u)]; 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 // 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); return (indices >> (8u * (index & ((1u << 2u) - 1u)))) & ((1u << 8u) - 1u);
#endif #endif
@ -93,14 +94,23 @@ fn cluster_debug_visualization(
output_color.a output_color.a
); );
#endif // CLUSTERED_FORWARD_DEBUG_Z_SLICES #endif // CLUSTERED_FORWARD_DEBUG_Z_SLICES
#ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY #ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_COMPLEXITY
// NOTE: This debug mode visualises the number of lights within the cluster that contains // NOTE: This debug mode visualises the number of clusterable objects within
// the fragment. It shows a sort of lighting complexity measure. // the cluster that contains the fragment. It shows a sort of cluster
// complexity measure.
let cluster_overlay_alpha = 0.1; let cluster_overlay_alpha = 0.1;
let max_light_complexity_per_cluster = 64.0; 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_light_complexity_per_cluster, f32(offset_and_counts[1] + offset_and_counts[2])); output_color.r = (1.0 - cluster_overlay_alpha) * output_color.r + cluster_overlay_alpha *
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]))); smoothStep(
#endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY 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 #ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY
// NOTE: Visualizes the cluster to which the fragment belongs // NOTE: Visualizes the cluster to which the fragment belongs
let cluster_overlay_alpha = 0.1; let cluster_overlay_alpha = 0.1;

View file

@ -173,7 +173,7 @@ pub fn extract_lights(
mut commands: Commands, mut commands: Commands,
point_light_shadow_map: Extract<Res<PointLightShadowMap>>, point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>, directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
global_point_lights: Extract<Res<GlobalVisiblePointLights>>, global_point_lights: Extract<Res<GlobalVisibleClusterableObjects>>,
point_lights: Extract< point_lights: Extract<
Query<( Query<(
&PointLight, &PointLight,
@ -513,7 +513,7 @@ pub fn prepare_lights(
mut texture_cache: ResMut<TextureCache>, mut texture_cache: ResMut<TextureCache>,
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>, render_queue: Res<RenderQueue>,
mut global_light_meta: ResMut<GlobalLightMeta>, mut global_light_meta: ResMut<GlobalClusterableObjectMeta>,
mut light_meta: ResMut<LightMeta>, mut light_meta: ResMut<LightMeta>,
views: Query<( views: Query<(
Entity, Entity,
@ -634,7 +634,7 @@ pub fn prepare_lights(
// point light shadows and `spot_light_shadow_maps_count` spot light shadow maps, // 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. // - 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, _)| { point_lights.sort_by(|(entity_1, light_1, _), (entity_2, light_2, _)| {
crate::cluster::point_light_order( crate::cluster::clusterable_object_order(
( (
entity_1, entity_1,
&light_1.shadows_enabled, &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, light_custom_data,
// premultiply color by intensity // premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3] // 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 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); .write_buffer(&render_device, &render_queue);
live_shadow_mapping_lights.clear(); live_shadow_mapping_lights.clear();

View file

@ -41,9 +41,9 @@ use crate::{
self, IrradianceVolume, RenderViewIrradianceVolumeBindGroupEntries, self, IrradianceVolume, RenderViewIrradianceVolumeBindGroupEntries,
IRRADIANCE_VOLUMES_ARE_USABLE, IRRADIANCE_VOLUMES_ARE_USABLE,
}, },
prepass, FogMeta, GlobalLightMeta, GpuFog, GpuLights, GpuPointLights, LightMeta, prepass, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights,
LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, RenderViewLightProbes, LightMeta, LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey,
ScreenSpaceAmbientOcclusionTextures, ScreenSpaceReflectionsBuffer, RenderViewLightProbes, ScreenSpaceAmbientOcclusionTextures, ScreenSpaceReflectionsBuffer,
ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
}; };
@ -229,13 +229,13 @@ fn layout_entries(
), ),
// Directional Shadow Texture Array Sampler // Directional Shadow Texture Array Sampler
(5, sampler(SamplerBindingType::Comparison)), (5, sampler(SamplerBindingType::Comparison)),
// PointLights // ClusterableObjects
( (
6, 6,
buffer_layout( buffer_layout(
clustered_forward_buffer_binding_type, clustered_forward_buffer_binding_type,
false, false,
Some(GpuPointLights::min_size( Some(GpuClusterableObjects::min_size(
clustered_forward_buffer_binding_type, clustered_forward_buffer_binding_type,
)), )),
), ),
@ -246,9 +246,11 @@ fn layout_entries(
buffer_layout( buffer_layout(
clustered_forward_buffer_binding_type, clustered_forward_buffer_binding_type,
false, false,
Some(ViewClusterBindings::min_size_cluster_light_index_lists( Some(
clustered_forward_buffer_binding_type, ViewClusterBindings::min_size_clusterable_object_index_lists(
)), clustered_forward_buffer_binding_type,
),
),
), ),
), ),
// ClusterOffsetsAndCounts // ClusterOffsetsAndCounts
@ -446,7 +448,7 @@ pub fn prepare_mesh_view_bind_groups(
mesh_pipeline: Res<MeshPipeline>, mesh_pipeline: Res<MeshPipeline>,
shadow_samplers: Res<ShadowSamplers>, shadow_samplers: Res<ShadowSamplers>,
light_meta: Res<LightMeta>, light_meta: Res<LightMeta>,
global_light_meta: Res<GlobalLightMeta>, global_light_meta: Res<GlobalClusterableObjectMeta>,
fog_meta: Res<FogMeta>, fog_meta: Res<FogMeta>,
view_uniforms: Res<ViewUniforms>, view_uniforms: Res<ViewUniforms>,
views: Query<( views: Query<(
@ -476,7 +478,7 @@ pub fn prepare_mesh_view_bind_groups(
if let ( if let (
Some(view_binding), Some(view_binding),
Some(light_binding), Some(light_binding),
Some(point_light_binding), Some(clusterable_objects_binding),
Some(globals), Some(globals),
Some(fog_binding), Some(fog_binding),
Some(light_probes_binding), Some(light_probes_binding),
@ -485,7 +487,7 @@ pub fn prepare_mesh_view_bind_groups(
) = ( ) = (
view_uniforms.uniforms.binding(), view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.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(), globals_buffer.buffer.binding(),
fog_meta.gpu_fogs.binding(), fog_meta.gpu_fogs.binding(),
light_probes_buffer.binding(), light_probes_buffer.binding(),
@ -524,8 +526,13 @@ pub fn prepare_mesh_view_bind_groups(
(3, &shadow_samplers.point_light_sampler), (3, &shadow_samplers.point_light_sampler),
(4, &shadow_bindings.directional_light_depth_texture_view), (4, &shadow_bindings.directional_light_depth_texture_view),
(5, &shadow_samplers.directional_light_sampler), (5, &shadow_samplers.directional_light_sampler),
(6, point_light_binding.clone()), (6, clusterable_objects_binding.clone()),
(7, cluster_bindings.light_index_lists_binding().unwrap()), (
7,
cluster_bindings
.clusterable_object_index_lists_binding()
.unwrap(),
),
(8, cluster_bindings.offsets_and_counts_binding().unwrap()), (8, cluster_bindings.offsets_and_counts_binding().unwrap()),
(9, globals.clone()), (9, globals.clone()),
(10, fog_binding.clone()), (10, fog_binding.clone()),

View file

@ -22,12 +22,12 @@
@group(0) @binding(5) var directional_shadow_textures_sampler: sampler_comparison; @group(0) @binding(5) var directional_shadow_textures_sampler: sampler_comparison;
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
@group(0) @binding(6) var<storage> point_lights: types::PointLights; @group(0) @binding(6) var<storage> clusterable_objects: types::ClusterableObjects;
@group(0) @binding(7) var<storage> cluster_light_index_lists: types::ClusterLightIndexLists; @group(0) @binding(7) var<storage> clusterable_object_index_lists: types::ClusterLightIndexLists;
@group(0) @binding(8) var<storage> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; @group(0) @binding(8) var<storage> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
#else #else
@group(0) @binding(6) var<uniform> point_lights: types::PointLights; @group(0) @binding(6) var<uniform> clusterable_objects: types::ClusterableObjects;
@group(0) @binding(7) var<uniform> cluster_light_index_lists: types::ClusterLightIndexLists; @group(0) @binding(7) var<uniform> clusterable_object_index_lists: types::ClusterLightIndexLists;
@group(0) @binding(8) var<uniform> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; @group(0) @binding(8) var<uniform> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
#endif #endif

View file

@ -1,6 +1,6 @@
#define_import_path bevy_pbr::mesh_view_types #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 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 // For spot lights: the direction (x,z), spot_scale and spot_offset
light_custom_data: vec4<f32>, light_custom_data: vec4<f32>,
@ -88,8 +88,8 @@ const FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3u;
const FOG_MODE_ATMOSPHERIC: u32 = 4u; const FOG_MODE_ATMOSPHERIC: u32 = 4u;
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
struct PointLights { struct ClusterableObjects {
data: array<PointLight>, data: array<ClusterableObject>,
}; };
struct ClusterLightIndexLists { struct ClusterLightIndexLists {
data: array<u32>, data: array<u32>,
@ -98,11 +98,11 @@ struct ClusterOffsetsAndCounts {
data: array<vec4<u32>>, data: array<vec4<u32>>,
}; };
#else #else
struct PointLights { struct ClusterableObjects {
data: array<PointLight, 256u>, data: array<ClusterableObject, 256u>,
}; };
struct ClusterLightIndexLists { struct ClusterLightIndexLists {
// each u32 contains 4 u8 indices into the PointLights array // each u32 contains 4 u8 indices into the ClusterableObjects array
data: array<vec4<u32>, 1024u>, data: array<vec4<u32>, 1024u>,
}; };
struct ClusterOffsetsAndCounts { struct ClusterOffsetsAndCounts {

View file

@ -410,10 +410,10 @@ fn apply_pbr_lighting(
// Point lights (direct) // Point lights (direct)
for (var i: u32 = offset_and_counts[0]; i < offset_and_counts[0] + offset_and_counts[1]; i = i + 1u) { 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; var shadow: f32 = 1.0;
if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u 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); shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal);
} }
@ -432,7 +432,7 @@ fn apply_pbr_lighting(
// F0 = vec3<f32>(0.0) // F0 = vec3<f32>(0.0)
var transmitted_shadow: f32 = 1.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) 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); 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) // 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) { 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; var shadow: f32 = 1.0;
if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u 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); shadow = shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal);
} }
@ -467,7 +467,7 @@ fn apply_pbr_lighting(
// F0 = vec3<f32>(0.0) // F0 = vec3<f32>(0.0)
var transmitted_shadow: f32 = 1.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) 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); transmitted_shadow = shadows::fetch_spot_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal);
} }

View file

@ -447,7 +447,7 @@ fn point_light(light_id: u32, input: ptr<function, LightingInput>) -> vec3<f32>
let N = (*input).layers[LAYER_BASE].N; let N = (*input).layers[LAYER_BASE].N;
let V = (*input).V; 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 light_to_frag = (*light).position_radius.xyz - P;
let L = normalize(light_to_frag); let L = normalize(light_to_frag);
let distance_square = dot(light_to_frag, 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<function, LightingInput>) -> vec3<f32> {
// reuse the point light calculations // reuse the point light calculations
let point_light = point_light(light_id, input); 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 // reconstruct spot dir from x/z and y-direction flag
var spot_dir = vec3<f32>((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y); var spot_dir = vec3<f32>((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y);

View file

@ -14,7 +14,7 @@
const flip_z: vec3<f32> = vec3<f32>(1.0, 1.0, -1.0); const flip_z: vec3<f32> = vec3<f32>(1.0, 1.0, -1.0);
fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 { fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> 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 // 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 // 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<f32>, surface_normal: v
} }
fn fetch_spot_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 { fn fetch_spot_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> 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; let surface_to_light = (*light).position_radius.xyz - frag_position.xyz;

View file

@ -7,7 +7,7 @@ use bevy::{
color::palettes::css::DEEP_PINK, color::palettes::css::DEEP_PINK,
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
math::{DVec2, DVec3}, math::{DVec2, DVec3},
pbr::{ExtractedPointLight, GlobalLightMeta}, pbr::{ExtractedPointLight, GlobalClusterableObjectMeta},
prelude::*, prelude::*,
render::{camera::ScalingMode, Render, RenderApp, RenderSet}, render::{camera::ScalingMode, Render, RenderApp, RenderSet},
window::{PresentMode, WindowResolution}, window::{PresentMode, WindowResolution},
@ -170,7 +170,7 @@ fn print_visible_light_count(
time: Res<Time>, time: Res<Time>,
mut timer: Local<PrintingTimer>, mut timer: Local<PrintingTimer>,
visible: Query<&ExtractedPointLight>, visible: Query<&ExtractedPointLight>,
global_light_meta: Res<GlobalLightMeta>, global_light_meta: Res<GlobalClusterableObjectMeta>,
) { ) {
timer.0.tick(time.delta()); timer.0.tick(time.delta());