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 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<GlobalVisiblePointLights>,
mut global_clusterable_objects: ResMut<GlobalVisibleClusterableObjects>,
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<Vec<PointLightAssignmentData>>,
mut clusterable_objects: Local<Vec<ClusterableObjectAssignmentData>>,
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>>,
) {
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::<VisiblePointLights>();
if visible_clusterable_objects.is_some() {
commands
.entity(view_entity)
.remove::<VisibleClusterableObjects>();
}
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<Entity>| {
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<Sphere> {
fn project_to_plane_z(z_object: Sphere, z_plane: HalfSpace) -> Option<Sphere> {
// 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<Sphere> {
// = 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<Sphere> {
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(),
})
}

View file

@ -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<VisiblePointLights>,
pub(crate) clusterable_objects: Vec<VisibleClusterableObjects>,
}
#[derive(Clone, Component, Debug, Default)]
pub struct VisiblePointLights {
pub struct VisibleClusterableObjects {
pub(crate) entities: Vec<Entity>,
pub point_light_count: usize,
pub spot_light_count: usize,
}
#[derive(Resource, Default)]
pub struct GlobalVisiblePointLights {
pub struct GlobalVisibleClusterableObjects {
pub(crate) entities: HashSet<Entity>,
}
#[derive(Resource)]
pub struct GlobalLightMeta {
pub gpu_point_lights: GpuPointLights,
pub struct GlobalClusterableObjectMeta {
pub gpu_clusterable_objects: GpuClusterableObjects,
pub entity_to_index: EntityHashMap<usize>,
}
#[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<GpuPointLightsUniform>),
Storage(StorageBuffer<GpuPointLightsStorage>),
pub enum GpuClusterableObjects {
Uniform(UniformBuffer<GpuClusterableObjectsUniform>),
Storage(StorageBuffer<GpuClusterableObjectsStorage>),
}
#[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<GpuPointLight>,
data: Vec<GpuClusterableObject>,
}
#[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<ExtractedClustersPointLightsElement>,
pub struct ExtractedClusterableObjects {
data: Vec<ExtractedClusterableObjectElement>,
}
#[derive(ShaderType)]
@ -194,7 +194,7 @@ struct GpuClusterOffsetsAndCountsUniform {
}
#[derive(ShaderType, Default)]
struct GpuClusterLightIndexListsStorage {
struct GpuClusterableObjectIndexListsStorage {
#[size(runtime)]
data: Vec<u32>,
}
@ -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<GpuClusterLightIndexListsUniform>,
clusterable_object_index_lists: UniformBuffer<GpuClusterableObjectIndexListsUniform>,
// NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>,
},
Storage {
cluster_light_index_lists: StorageBuffer<GpuClusterLightIndexListsStorage>,
clusterable_object_index_lists: StorageBuffer<GpuClusterableObjectIndexListsStorage>,
cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>,
},
}
@ -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<Item = &Entity> {
self.entities.iter()
@ -380,7 +381,7 @@ impl VisiblePointLights {
}
}
impl GlobalVisiblePointLights {
impl GlobalVisibleClusterableObjects {
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &Entity> {
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<GpuPointLight>) {
pub(crate) fn set(&mut self, mut clusterable_objects: Vec<GpuClusterableObject>) {
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<BindingResource> {
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<RenderDevice>,
render_queue: Res<RenderQueue>,
mesh_pipeline: Res<MeshPipeline>,
global_light_meta: Res<GlobalLightMeta>,
views: Query<(Entity, &ExtractedClustersPointLights)>,
global_clusterable_object_meta: Res<GlobalClusterableObjectMeta>,
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<BindingResource> {
pub fn clusterable_object_index_lists_binding(&self) -> Option<BindingResource> {
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]),

View file

@ -298,7 +298,7 @@ impl Plugin for PbrPlugin {
.register_type::<FogSettings>()
.register_type::<ShadowFilteringMethod>()
.init_resource::<AmbientLight>()
.init_resource::<GlobalVisiblePointLights>()
.init_resource::<GlobalVisibleClusterableObjects>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
.register_type::<DefaultOpaqueRendererMethod>()
@ -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::<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!
pub fn update_point_light_frusta(
global_lights: Res<GlobalVisiblePointLights>,
global_lights: Res<GlobalVisibleClusterableObjects>,
mut views: Query<
(Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta),
Or<(Changed<GlobalTransform>, Changed<PointLight>)>,
@ -605,7 +605,7 @@ pub fn update_point_light_frusta(
}
pub fn update_spot_light_frusta(
global_lights: Res<GlobalVisiblePointLights>,
global_lights: Res<GlobalVisibleClusterableObjects>,
mut views: Query<
(Entity, &GlobalTransform, &SpotLight, &mut Frustum),
Or<(Changed<GlobalTransform>, Changed<SpotLight>)>,
@ -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,

View file

@ -53,13 +53,14 @@ fn unpack_offset_and_counts(cluster_index: u32) -> vec3<u32> {
#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;

View file

@ -173,7 +173,7 @@ pub fn extract_lights(
mut commands: Commands,
point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
global_point_lights: Extract<Res<GlobalVisiblePointLights>>,
global_point_lights: Extract<Res<GlobalVisibleClusterableObjects>>,
point_lights: Extract<
Query<(
&PointLight,
@ -513,7 +513,7 @@ pub fn prepare_lights(
mut texture_cache: ResMut<TextureCache>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut global_light_meta: ResMut<GlobalLightMeta>,
mut global_light_meta: ResMut<GlobalClusterableObjectMeta>,
mut light_meta: ResMut<LightMeta>,
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();

View file

@ -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<MeshPipeline>,
shadow_samplers: Res<ShadowSamplers>,
light_meta: Res<LightMeta>,
global_light_meta: Res<GlobalLightMeta>,
global_light_meta: Res<GlobalClusterableObjectMeta>,
fog_meta: Res<FogMeta>,
view_uniforms: Res<ViewUniforms>,
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()),

View file

@ -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<storage> point_lights: types::PointLights;
@group(0) @binding(7) var<storage> cluster_light_index_lists: types::ClusterLightIndexLists;
@group(0) @binding(6) var<storage> clusterable_objects: types::ClusterableObjects;
@group(0) @binding(7) var<storage> clusterable_object_index_lists: types::ClusterLightIndexLists;
@group(0) @binding(8) var<storage> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
#else
@group(0) @binding(6) var<uniform> point_lights: types::PointLights;
@group(0) @binding(7) var<uniform> cluster_light_index_lists: types::ClusterLightIndexLists;
@group(0) @binding(6) var<uniform> clusterable_objects: types::ClusterableObjects;
@group(0) @binding(7) var<uniform> clusterable_object_index_lists: types::ClusterLightIndexLists;
@group(0) @binding(8) var<uniform> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
#endif

View file

@ -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<f32>,
@ -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<PointLight>,
struct ClusterableObjects {
data: array<ClusterableObject>,
};
struct ClusterLightIndexLists {
data: array<u32>,
@ -98,11 +98,11 @@ struct ClusterOffsetsAndCounts {
data: array<vec4<u32>>,
};
#else
struct PointLights {
data: array<PointLight, 256u>,
struct ClusterableObjects {
data: array<ClusterableObject, 256u>,
};
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>,
};
struct ClusterOffsetsAndCounts {

View file

@ -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<f32>(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<f32>(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);
}

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 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<function, LightingInput>) -> vec3<f32> {
// 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<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);
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
// 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 {
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;

View file

@ -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<Time>,
mut timer: Local<PrintingTimer>,
visible: Query<&ExtractedPointLight>,
global_light_meta: Res<GlobalLightMeta>,
global_light_meta: Res<GlobalClusterableObjectMeta>,
) {
timer.0.tick(time.delta());