Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_light/src/cluster/mod.rs
6849 views
1
//! Spatial clustering of objects, currently just point and spot lights.
2
3
use bevy_asset::Handle;
4
use bevy_camera::{
5
visibility::{self, Visibility, VisibilityClass},
6
Camera, Camera3d,
7
};
8
use bevy_ecs::{
9
component::Component,
10
entity::Entity,
11
query::{With, Without},
12
reflect::ReflectComponent,
13
resource::Resource,
14
system::{Commands, Query},
15
};
16
use bevy_image::Image;
17
use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _};
18
use bevy_platform::collections::HashSet;
19
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
20
use bevy_transform::components::Transform;
21
use tracing::warn;
22
23
pub mod assign;
24
25
#[cfg(test)]
26
mod test;
27
28
// Clustered-forward rendering notes
29
// The main initial reference material used was this rather accessible article:
30
// http://www.aortiz.me/2018/12/21/CG.html
31
// Some inspiration was taken from “Practical Clustered Shading” which is part 2 of:
32
// https://efficientshading.com/2015/01/01/real-time-many-light-management-and-shadows-with-clustered-shading/
33
// (Also note that Part 3 of the above shows how we could support the shadow mapping for many lights.)
34
// The z-slicing method mentioned in the aortiz article is originally from Tiago Sousa's Siggraph 2016 talk about Doom 2016:
35
// http://advances.realtimerendering.com/s2016/Siggraph2016_idTech6.pdf
36
37
#[derive(Resource)]
38
pub struct GlobalClusterSettings {
39
pub supports_storage_buffers: bool,
40
pub clustered_decals_are_usable: bool,
41
pub max_uniform_buffer_clusterable_objects: usize,
42
pub view_cluster_bindings_max_indices: usize,
43
}
44
45
/// Configure the far z-plane mode used for the furthest depth slice for clustered forward
46
/// rendering
47
#[derive(Debug, Copy, Clone, Reflect)]
48
#[reflect(Clone)]
49
pub enum ClusterFarZMode {
50
/// Calculate the required maximum z-depth based on currently visible
51
/// clusterable objects. Makes better use of available clusters, speeding
52
/// up GPU lighting operations at the expense of some CPU time and using
53
/// more indices in the clusterable object index lists.
54
MaxClusterableObjectRange,
55
/// Constant max z-depth
56
Constant(f32),
57
}
58
59
/// Configure the depth-slicing strategy for clustered forward rendering
60
#[derive(Debug, Copy, Clone, Reflect)]
61
#[reflect(Default, Clone)]
62
pub struct ClusterZConfig {
63
/// Far `Z` plane of the first depth slice
64
pub first_slice_depth: f32,
65
/// Strategy for how to evaluate the far `Z` plane of the furthest depth slice
66
pub far_z_mode: ClusterFarZMode,
67
}
68
69
/// Configuration of the clustering strategy for clustered forward rendering
70
#[derive(Debug, Copy, Clone, Component, Reflect)]
71
#[reflect(Component, Debug, Default, Clone)]
72
pub enum ClusterConfig {
73
/// Disable cluster calculations for this view
74
None,
75
/// One single cluster. Optimal for low-light complexity scenes or scenes where
76
/// most lights affect the entire scene.
77
Single,
78
/// Explicit `X`, `Y` and `Z` counts (may yield non-square `X/Y` clusters depending on the aspect ratio)
79
XYZ {
80
dimensions: UVec3,
81
z_config: ClusterZConfig,
82
/// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding
83
/// the available cluster-object index limit
84
dynamic_resizing: bool,
85
},
86
/// Fixed number of `Z` slices, `X` and `Y` calculated to give square clusters
87
/// with at most total clusters. For top-down games where lights will generally always be within a
88
/// short depth range, it may be useful to use this configuration with 1 or few `Z` slices. This
89
/// would reduce the number of lights per cluster by distributing more clusters in screen space
90
/// `X/Y` which matches how lights are distributed in the scene.
91
FixedZ {
92
total: u32,
93
z_slices: u32,
94
z_config: ClusterZConfig,
95
/// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding
96
/// the available clusterable object index limit
97
dynamic_resizing: bool,
98
},
99
}
100
101
#[derive(Component, Debug, Default)]
102
pub struct Clusters {
103
/// Tile size
104
pub tile_size: UVec2,
105
/// Number of clusters in `X` / `Y` / `Z` in the view frustum
106
pub dimensions: UVec3,
107
/// Distance to the far plane of the first depth slice. The first depth slice is special
108
/// and explicitly-configured to avoid having unnecessarily many slices close to the camera.
109
pub near: f32,
110
pub far: f32,
111
pub clusterable_objects: Vec<VisibleClusterableObjects>,
112
}
113
114
/// The [`VisibilityClass`] used for clusterables (decals, point lights, directional lights, and spot lights).
115
///
116
/// [`VisibilityClass`]: bevy_camera::visibility::VisibilityClass
117
pub struct ClusterVisibilityClass;
118
119
#[derive(Clone, Component, Debug, Default)]
120
pub struct VisibleClusterableObjects {
121
pub entities: Vec<Entity>,
122
pub counts: ClusterableObjectCounts,
123
}
124
125
#[derive(Resource, Default)]
126
pub struct GlobalVisibleClusterableObjects {
127
pub(crate) entities: HashSet<Entity>,
128
}
129
130
/// Stores the number of each type of clusterable object in a single cluster.
131
///
132
/// Note that `reflection_probes` and `irradiance_volumes` won't be clustered if
133
/// fewer than 3 SSBOs are available, which usually means on WebGL 2.
134
#[derive(Clone, Copy, Default, Debug)]
135
pub struct ClusterableObjectCounts {
136
/// The number of point lights in the cluster.
137
pub point_lights: u32,
138
/// The number of spot lights in the cluster.
139
pub spot_lights: u32,
140
/// The number of reflection probes in the cluster.
141
pub reflection_probes: u32,
142
/// The number of irradiance volumes in the cluster.
143
pub irradiance_volumes: u32,
144
/// The number of decals in the cluster.
145
pub decals: u32,
146
}
147
148
/// An object that projects a decal onto surfaces within its bounds.
149
///
150
/// Conceptually, a clustered decal is a 1×1×1 cube centered on its origin. It
151
/// projects the given [`Self::image`] onto surfaces in the -Z direction (thus
152
/// you may find [`Transform::looking_at`] useful).
153
///
154
/// Clustered decals are the highest-quality types of decals that Bevy supports,
155
/// but they require bindless textures. This means that they presently can't be
156
/// used on WebGL 2, WebGPU, macOS, or iOS. Bevy's clustered decals can be used
157
/// with forward or deferred rendering and don't require a prepass.
158
#[derive(Component, Debug, Clone, Reflect)]
159
#[reflect(Component, Debug, Clone)]
160
#[require(Transform, Visibility, VisibilityClass)]
161
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
162
pub struct ClusteredDecal {
163
/// The image that the clustered decal projects.
164
///
165
/// This must be a 2D image. If it has an alpha channel, it'll be alpha
166
/// blended with the underlying surface and/or other decals. All decal
167
/// images in the scene must use the same sampler.
168
pub image: Handle<Image>,
169
170
/// An application-specific tag you can use for any purpose you want.
171
///
172
/// See the `clustered_decals` example for an example of use.
173
pub tag: u32,
174
}
175
176
impl Default for ClusterZConfig {
177
fn default() -> Self {
178
Self {
179
first_slice_depth: 5.0,
180
far_z_mode: ClusterFarZMode::MaxClusterableObjectRange,
181
}
182
}
183
}
184
185
impl Default for ClusterConfig {
186
fn default() -> Self {
187
// 24 depth slices, square clusters with at most 4096 total clusters
188
// use max light distance as clusters max `Z`-depth, first slice extends to 5.0
189
Self::FixedZ {
190
total: 4096,
191
z_slices: 24,
192
z_config: ClusterZConfig::default(),
193
dynamic_resizing: true,
194
}
195
}
196
}
197
198
impl ClusterConfig {
199
fn dimensions_for_screen_size(&self, screen_size: UVec2) -> UVec3 {
200
match &self {
201
ClusterConfig::None => UVec3::ZERO,
202
ClusterConfig::Single => UVec3::ONE,
203
ClusterConfig::XYZ { dimensions, .. } => *dimensions,
204
ClusterConfig::FixedZ {
205
total, z_slices, ..
206
} => {
207
let aspect_ratio: f32 = AspectRatio::try_from_pixels(screen_size.x, screen_size.y)
208
.expect("Failed to calculate aspect ratio for Cluster: screen dimensions must be positive, non-zero values")
209
.ratio();
210
let mut z_slices = *z_slices;
211
if *total < z_slices {
212
warn!("ClusterConfig has more z-slices than total clusters!");
213
z_slices = *total;
214
}
215
let per_layer = *total as f32 / z_slices as f32;
216
217
let y = f32::sqrt(per_layer / aspect_ratio);
218
219
let mut x = (y * aspect_ratio) as u32;
220
let mut y = y as u32;
221
222
// check extremes
223
if x == 0 {
224
x = 1;
225
y = per_layer as u32;
226
}
227
if y == 0 {
228
x = per_layer as u32;
229
y = 1;
230
}
231
232
UVec3::new(x, y, z_slices)
233
}
234
}
235
}
236
237
fn first_slice_depth(&self) -> f32 {
238
match self {
239
ClusterConfig::None | ClusterConfig::Single => 0.0,
240
ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
241
z_config.first_slice_depth
242
}
243
}
244
}
245
246
fn far_z_mode(&self) -> ClusterFarZMode {
247
match self {
248
ClusterConfig::None => ClusterFarZMode::Constant(0.0),
249
ClusterConfig::Single => ClusterFarZMode::MaxClusterableObjectRange,
250
ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
251
z_config.far_z_mode
252
}
253
}
254
}
255
256
fn dynamic_resizing(&self) -> bool {
257
match self {
258
ClusterConfig::None | ClusterConfig::Single => false,
259
ClusterConfig::XYZ {
260
dynamic_resizing, ..
261
}
262
| ClusterConfig::FixedZ {
263
dynamic_resizing, ..
264
} => *dynamic_resizing,
265
}
266
}
267
}
268
269
impl Clusters {
270
fn update(&mut self, screen_size: UVec2, requested_dimensions: UVec3) {
271
debug_assert!(
272
requested_dimensions.x > 0 && requested_dimensions.y > 0 && requested_dimensions.z > 0
273
);
274
275
let tile_size = (screen_size.as_vec2() / requested_dimensions.xy().as_vec2())
276
.ceil()
277
.as_uvec2()
278
.max(UVec2::ONE);
279
self.tile_size = tile_size;
280
self.dimensions = (screen_size.as_vec2() / tile_size.as_vec2())
281
.ceil()
282
.as_uvec2()
283
.extend(requested_dimensions.z)
284
.max(UVec3::ONE);
285
286
// NOTE: Maximum 4096 clusters due to uniform buffer size constraints
287
debug_assert!(self.dimensions.x * self.dimensions.y * self.dimensions.z <= 4096);
288
}
289
fn clear(&mut self) {
290
self.tile_size = UVec2::ONE;
291
self.dimensions = UVec3::ZERO;
292
self.near = 0.0;
293
self.far = 0.0;
294
self.clusterable_objects.clear();
295
}
296
}
297
298
pub fn add_clusters(
299
mut commands: Commands,
300
cameras: Query<(Entity, Option<&ClusterConfig>, &Camera), (Without<Clusters>, With<Camera3d>)>,
301
) {
302
for (entity, config, camera) in &cameras {
303
if !camera.is_active {
304
continue;
305
}
306
307
let config = config.copied().unwrap_or_default();
308
// actual settings here don't matter - they will be overwritten in
309
// `assign_objects_to_clusters``
310
commands
311
.entity(entity)
312
.insert((Clusters::default(), config));
313
}
314
}
315
316
impl VisibleClusterableObjects {
317
#[inline]
318
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
319
self.entities.iter()
320
}
321
322
#[inline]
323
pub fn len(&self) -> usize {
324
self.entities.len()
325
}
326
327
#[inline]
328
pub fn is_empty(&self) -> bool {
329
self.entities.is_empty()
330
}
331
}
332
333
impl GlobalVisibleClusterableObjects {
334
#[inline]
335
pub fn iter(&self) -> impl Iterator<Item = &Entity> {
336
self.entities.iter()
337
}
338
339
#[inline]
340
pub fn contains(&self, entity: Entity) -> bool {
341
self.entities.contains(&entity)
342
}
343
}
344
345