mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
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:
parent
ab2add64fa
commit
ad6872275f
13 changed files with 439 additions and 355 deletions
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
Loading…
Reference in a new issue