bevy_pbr/light_probe/mod.rs
1//! Light probes for baked global illumination.
2
3use bevy_app::{App, Plugin};
4use bevy_asset::AssetId;
5use bevy_camera::{visibility::VisibleEntities, Camera3d};
6use bevy_derive::{Deref, DerefMut};
7use bevy_ecs::{
8 component::Component,
9 entity::Entity,
10 query::{QueryData, ReadOnlyQueryData, With},
11 resource::Resource,
12 schedule::IntoScheduleConfigs,
13 system::{Commands, Local, Query, Res, ResMut},
14};
15use bevy_extract::extract_instances::ExtractInstancesPlugin;
16use bevy_image::Image;
17use bevy_light::{
18 cluster::ClusterVisibilityClass, EnvironmentMapLight, IrradianceVolume, LightProbe,
19};
20use bevy_math::{Affine3A, FloatOrd, Mat4, Quat, Vec3, Vec4};
21use bevy_platform::collections::HashMap;
22use bevy_render::{
23 render_asset::RenderAssets,
24 render_resource::{DynamicUniformBuffer, Sampler, ShaderType, TextureView},
25 renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue},
26 settings::WgpuFeatures,
27 sync_world::{MainEntity, MainEntityHashMap, RenderEntity},
28 texture::{FallbackImage, GpuImage},
29 view::ExtractedView,
30 Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderSystems,
31};
32use bevy_shader::load_shader_library;
33use bevy_transform::prelude::GlobalTransform;
34use bitflags::bitflags;
35use tracing::error;
36
37use core::{any::TypeId, hash::Hash, ops::Deref};
38
39use crate::{
40 extract_clusters_for_cpu_clustering, generate::EnvironmentMapGenerationPlugin,
41 gpu::extract_clusters_for_gpu_clustering, light_probe::environment_map::EnvironmentMapIds,
42};
43
44pub mod environment_map;
45pub mod generate;
46pub mod irradiance_volume;
47
48/// The maximum number of each type of light probe that each view will consider.
49///
50/// Because the fragment shader does a linear search through the list for each
51/// fragment, this number needs to be relatively small.
52pub const MAX_VIEW_LIGHT_PROBES: usize = 8;
53
54/// How many texture bindings are used in the fragment shader, *not* counting
55/// environment maps or irradiance volumes.
56const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;
57
58/// Adds support for light probes: cuboid bounding regions that apply global
59/// illumination to objects within them.
60///
61/// This also adds support for view environment maps: diffuse and specular
62/// cubemaps applied to all objects that a view renders.
63pub struct LightProbePlugin;
64
65/// A GPU type that stores information about a light probe.
66#[derive(Clone, Copy, ShaderType, Default)]
67struct RenderLightProbe {
68 /// The transform from the world space to the model space. This is used to
69 /// efficiently check for bounding box intersection.
70 light_from_world_transposed: [Vec4; 3],
71
72 /// The falloff region, specified as a fraction of the light probe's
73 /// bounding box.
74 ///
75 /// See the comments in [`LightProbe`] for more details.
76 falloff: Vec3,
77
78 /// The radius of the bounding sphere that encompasses this light probe.
79 ///
80 /// This could be computed from [`Self::light_from_world_transposed`], but
81 /// that requires inverting a matrix, which we don't want to do in the GPU
82 /// light clustering kernel.
83 bounding_sphere_radius: f32,
84
85 /// The boundaries of the simulated space used for parallax correction,
86 /// specified as *half* extents in light probe space.
87 ///
88 /// If parallax correction is disabled in [`RenderLightProbe::flags`], this
89 /// field is ignored.
90 ///
91 /// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more
92 /// details.
93 parallax_correction_bounds: Vec3,
94
95 /// Scale factor applied to the light generated by this light probe.
96 ///
97 /// See the comment in [`EnvironmentMapLight`] for details.
98 intensity: f32,
99
100 /// The world-space position of this light probe.
101 ///
102 /// This could be computed from [`Self::light_from_world_transposed`], but
103 /// that requires inverting a matrix, which we don't want to do in the GPU
104 /// light clustering kernel.
105 world_position: Vec3,
106
107 /// The index of the texture or textures in the appropriate binding array or
108 /// arrays.
109 ///
110 /// For example, for reflection probes this is the index of the cubemap in
111 /// the diffuse and specular texture arrays.
112 texture_index: i32,
113
114 /// Various flags associated with the light probe: the bit value of
115 /// [`RenderLightProbeFlags`].
116 flags: u32,
117}
118
119/// A per-view shader uniform that specifies all the light probes that the view
120/// takes into account.
121#[derive(ShaderType)]
122pub struct LightProbesUniform {
123 /// The list of applicable reflection probes, sorted from nearest to the
124 /// camera to the farthest away from the camera.
125 reflection_probes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
126
127 /// The list of applicable irradiance volumes, sorted from nearest to the
128 /// camera to the farthest away from the camera.
129 irradiance_volumes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
130
131 /// The number of reflection probes in the list.
132 reflection_probe_count: i32,
133
134 /// The number of irradiance volumes in the list.
135 irradiance_volume_count: i32,
136
137 /// The index of the diffuse and specular environment maps associated with
138 /// the view itself. This is used as a fallback if no reflection probe in
139 /// the list contains the fragment.
140 view_cubemap_index: i32,
141
142 /// The smallest valid mipmap level for the specular environment cubemap
143 /// associated with the view.
144 smallest_specular_mip_level_for_view: u32,
145
146 /// World space rotation applied to environment map cubemaps associated with the view itself.
147 view_rotation: Vec4,
148
149 /// The intensity of the environment cubemap associated with the view.
150 ///
151 /// See the comment in [`EnvironmentMapLight`] for details.
152 intensity_for_view: f32,
153
154 /// Whether the environment map attached to the view affects the diffuse
155 /// lighting for lightmapped meshes.
156 ///
157 /// This will be 1 if the map does affect lightmapped meshes or 0 otherwise.
158 view_environment_map_affects_lightmapped_mesh_diffuse: u32,
159}
160
161/// A GPU buffer that stores information about all light probes.
162#[derive(Resource, Default, Deref, DerefMut)]
163pub struct LightProbesBuffer(DynamicUniformBuffer<LightProbesUniform>);
164
165/// A component attached to each camera in the render world that stores the
166/// index of the [`LightProbesUniform`] in the [`LightProbesBuffer`].
167#[derive(Component, Default, Deref, DerefMut)]
168pub struct ViewLightProbesUniformOffset(u32);
169
170/// Information that [`gather_light_probes`] keeps about each light probe.
171///
172/// This information is parameterized by the [`LightProbeComponent`] type. This
173/// will either be [`EnvironmentMapLight`] for reflection probes or
174/// [`IrradianceVolume`] for irradiance volumes.
175struct LightProbeInfo<C>
176where
177 C: LightProbeComponent,
178{
179 // The entity of the light probe in the main world.
180 main_entity: MainEntity,
181
182 // The transform from world space to light probe space.
183 // Stored as the transpose of the inverse transform to compress the structure
184 // on the GPU (from 4 `Vec4`s to 3 `Vec4`s). The shader will transpose it
185 // to recover the original inverse transform.
186 light_from_world: [Vec4; 3],
187
188 // The transform from light probe space to world space.
189 world_from_light: Affine3A,
190
191 /// The radius of the bounding sphere that encompasses this light probe.
192 ///
193 /// This could be computed from [`Self::light_from_world`], but that
194 /// requires inverting a matrix, which we don't want to do in the GPU light
195 /// clustering kernel.
196 bounding_sphere_radius: f32,
197
198 // The falloff region, specified as a fraction of the light probe's
199 // bounding box.
200 //
201 // See the comments in [`LightProbe`] for more details.
202 falloff: Vec3,
203
204 /// The boundaries of the simulated space used for parallax correction,
205 /// specified as *half* extents in light probe space.
206 ///
207 /// If parallax correction is disabled in [`RenderLightProbe::flags`], this
208 /// field is ignored.
209 ///
210 /// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more
211 /// details.
212 parallax_correction_bounds: Vec3,
213
214 // Scale factor applied to the diffuse and specular light generated by this
215 // reflection probe.
216 //
217 // See the comment in [`EnvironmentMapLight`] for details.
218 intensity: f32,
219
220 // Various flags associated with the light probe.
221 flags: RenderLightProbeFlags,
222
223 // The IDs of all assets associated with this light probe.
224 //
225 // Because each type of light probe component may reference different types
226 // of assets (e.g. a reflection probe references two cubemap assets while an
227 // irradiance volume references a single 3D texture asset), this is generic.
228 asset_id: C::AssetId,
229}
230
231bitflags! {
232 /// Various flags that can be associated with light probes.
233 #[derive(Clone, Copy, PartialEq, Debug)]
234 pub struct RenderLightProbeFlags: u8 {
235 /// Whether this light probe adds to the diffuse contribution of the
236 /// irradiance for meshes with lightmaps.
237 const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1;
238 /// Whether this light probe has parallax correction enabled.
239 ///
240 /// See the comments in [`bevy_light::NoParallaxCorrection`] for more
241 /// information.
242 const ENABLE_PARALLAX_CORRECTION = 2;
243 }
244}
245
246/// A component, part of the render world, that stores the mapping from asset ID
247/// or IDs to the texture index in the appropriate binding arrays.
248///
249/// Cubemap textures belonging to environment maps are collected into binding
250/// arrays, and the index of each texture is presented to the shader for runtime
251/// lookup. 3D textures belonging to reflection probes are likewise collected
252/// into binding arrays, and the shader accesses the 3D texture by index.
253///
254/// This component is attached to each view in the render world, because each
255/// view may have a different set of light probes that it considers and therefore
256/// the texture indices are per-view.
257#[derive(Component, Default)]
258pub struct RenderViewLightProbes<C>
259where
260 C: LightProbeComponent,
261{
262 /// The list of environment maps presented to the shader, in order.
263 pub binding_index_to_textures: Vec<C::AssetId>,
264
265 /// The reverse of `binding_index_to_cubemap`: a map from the texture ID to
266 /// the index in `binding_index_to_cubemap`.
267 cubemap_to_binding_index: HashMap<C::AssetId, u32>,
268
269 /// Information about each light probe, ready for upload to the GPU, sorted
270 /// in order from closest to the camera to farthest.
271 ///
272 /// Note that this is not necessarily ordered by binding index. So don't
273 /// write code like
274 /// `render_light_probes[cubemap_to_binding_index[asset_id]]`; instead
275 /// search for the light probe with the appropriate binding index in this
276 /// array.
277 render_light_probes: Vec<RenderLightProbe>,
278
279 /// A mapping from the main world entity to the index in
280 /// `render_light_probes`.
281 pub main_entity_to_render_light_probe_index: MainEntityHashMap<u32>,
282
283 /// Information needed to render the light probe attached directly to the
284 /// view, if applicable.
285 ///
286 /// A light probe attached directly to a view represents a "global" light
287 /// probe that affects all objects not in the bounding region of any light
288 /// probe. Currently, the only light probe type that supports this is the
289 /// [`EnvironmentMapLight`].
290 view_light_probe_info: Option<C::ViewLightProbeInfo>,
291}
292
293/// A trait implemented by all components that represent light probes.
294///
295/// Currently, the two light probe types are [`EnvironmentMapLight`] and
296/// [`IrradianceVolume`], for reflection probes and irradiance volumes
297/// respectively.
298///
299/// Most light probe systems are written to be generic over the type of light
300/// probe. This allows much of the code to be shared and enables easy addition
301/// of more light probe types (e.g. real-time reflection planes) in the future.
302pub trait LightProbeComponent: Send + Sync + Component + Sized {
303 /// Holds [`AssetId`]s of the texture or textures that this light probe
304 /// references.
305 ///
306 /// This can just be [`AssetId`] if the light probe only references one
307 /// texture. If it references multiple textures, it will be a structure
308 /// containing those asset IDs.
309 type AssetId: Send + Sync + Clone + Eq + Hash;
310
311 /// If the light probe can be attached to the view itself (as opposed to a
312 /// cuboid region within the scene), this contains the information that will
313 /// be passed to the GPU in order to render it. Otherwise, this will be
314 /// `()`.
315 ///
316 /// Currently, only reflection probes (i.e. [`EnvironmentMapLight`]) can be
317 /// attached directly to views.
318 type ViewLightProbeInfo: Send + Sync + Default;
319
320 /// Any additional query data needed to determine the
321 /// [`RenderLightProbeFlags`] for this light probe.
322 type QueryData: ReadOnlyQueryData;
323
324 /// Returns the asset ID or asset IDs of the texture or textures referenced
325 /// by this light probe.
326 fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId>;
327
328 /// Returns the intensity of this light probe.
329 ///
330 /// This is a scaling factor that will be multiplied by the value or values
331 /// sampled from the texture.
332 fn intensity(&self) -> f32;
333
334 /// Returns the appropriate value of [`RenderLightProbeFlags`] for this
335 /// component.
336 fn flags(
337 &self,
338 query_components: &<Self::QueryData as QueryData>::Item<'_, '_>,
339 ) -> RenderLightProbeFlags;
340
341 /// Creates an instance of [`RenderViewLightProbes`] containing all the
342 /// information needed to render this light probe.
343 ///
344 /// This is called for every light probe in view every frame.
345 fn create_render_view_light_probes(
346 view_component: Option<&Self>,
347 image_assets: &RenderAssets<GpuImage>,
348 ) -> RenderViewLightProbes<Self>;
349
350 /// Given the matrix value of the `GlobalTransform` of the light probe,
351 /// returns the matrix that transforms world positions into light probe
352 /// space.
353 ///
354 /// The default implementation simply returns the matrix unchanged, but some
355 /// light probes may want to perform other transforms.
356 fn get_world_from_light_matrix(&self, original_world_from_light: &Affine3A) -> Affine3A {
357 *original_world_from_light
358 }
359
360 /// Returns the appropriate parallax correction bounds, as half extents in
361 /// light probe space, for this component.
362 ///
363 /// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more
364 /// details.
365 fn parallax_correction_bounds(
366 &self,
367 _query_components: &<Self::QueryData as QueryData>::Item<'_, '_>,
368 ) -> Vec3 {
369 Vec3::ZERO
370 }
371}
372
373impl Plugin for LightProbePlugin {
374 fn build(&self, app: &mut App) {
375 load_shader_library!(app, "environment_map.wesl");
376 load_shader_library!(app, "irradiance_volume.wesl");
377
378 app.add_plugins((
379 EnvironmentMapGenerationPlugin,
380 ExtractInstancesPlugin::<EnvironmentMapIds, RenderApp>::new(),
381 ));
382
383 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
384 return;
385 };
386
387 render_app
388 .init_gpu_resource::<LightProbesBuffer>()
389 .add_systems(
390 ExtractSchedule,
391 gather_light_probes::<EnvironmentMapLight>
392 .before(extract_clusters_for_cpu_clustering)
393 .before(extract_clusters_for_gpu_clustering),
394 )
395 .add_systems(
396 ExtractSchedule,
397 gather_light_probes::<IrradianceVolume>
398 .before(extract_clusters_for_cpu_clustering)
399 .before(extract_clusters_for_gpu_clustering),
400 )
401 .add_systems(
402 Render,
403 upload_light_probes.in_set(RenderSystems::PrepareResources),
404 );
405 }
406}
407
408/// Gathers up all light probes of a single type in the scene and assigns them
409/// to views, performing frustum culling and distance sorting in the process.
410fn gather_light_probes<C>(
411 image_assets: Res<RenderAssets<GpuImage>>,
412 light_probe_query: Extract<Query<(Entity, &GlobalTransform, &LightProbe, &C, C::QueryData)>>,
413 view_query: Extract<
414 Query<(RenderEntity, &GlobalTransform, &VisibleEntities, Option<&C>), With<Camera3d>>,
415 >,
416 mut view_light_probe_info: Local<Vec<LightProbeInfo<C>>>,
417 mut commands: Commands,
418) where
419 C: LightProbeComponent,
420{
421 // Build up the light probes uniform and the key table.
422 for (view_entity, view_transform, visible_entities, view_component) in view_query.iter() {
423 view_light_probe_info.clear();
424 let visible_light_probes = visible_entities.get(TypeId::of::<ClusterVisibilityClass>());
425 for &main_entity in visible_light_probes {
426 let Ok(query_row) = light_probe_query.get(main_entity) else {
427 // This might not be a light probe. If so, ignore it.
428 continue;
429 };
430 // If we don't successfully create `LightProbeInfo`, that means
431 // the light probe hasn't loaded yet. We don't add such light
432 // probes to `view_light_probe_info` so that they don't waste
433 // space in the GPU light probe buffer, which has a limited
434 // size.
435 if let Some(light_probe_info) = LightProbeInfo::new(query_row, &image_assets) {
436 view_light_probe_info.push(light_probe_info);
437 }
438 }
439
440 // Sort by distance to camera.
441 view_light_probe_info.sort_by_cached_key(|light_probe_info| {
442 light_probe_info.camera_distance_sort_key(view_transform)
443 });
444
445 // Create the light probes list.
446 let mut render_view_light_probes =
447 C::create_render_view_light_probes(view_component, &image_assets);
448
449 // Gather up the light probes in the list.
450 render_view_light_probes.maybe_gather_light_probes(&view_light_probe_info);
451
452 // Record the per-view light probes.
453 if render_view_light_probes.is_empty() {
454 commands
455 .get_entity(view_entity)
456 .expect("View entity wasn't synced.")
457 .remove::<RenderViewLightProbes<C>>();
458 } else {
459 commands
460 .get_entity(view_entity)
461 .expect("View entity wasn't synced.")
462 .insert(render_view_light_probes);
463 }
464 }
465}
466
467// A system that runs after [`gather_light_probes`] and populates the GPU
468// uniforms with the results.
469//
470// Note that, unlike [`gather_light_probes`], this system is not generic over
471// the type of light probe. It collects light probes of all types together into
472// a single structure, ready to be passed to the shader.
473fn upload_light_probes(
474 mut commands: Commands,
475 views: Query<Entity, With<ExtractedView>>,
476 mut light_probes_buffer: ResMut<LightProbesBuffer>,
477 mut view_light_probes_query: Query<(
478 Option<&RenderViewLightProbes<EnvironmentMapLight>>,
479 Option<&RenderViewLightProbes<IrradianceVolume>>,
480 )>,
481 render_device: Res<RenderDevice>,
482 render_queue: Res<RenderQueue>,
483) {
484 // If there are no views, bail.
485 if views.is_empty() {
486 return;
487 }
488
489 // Initialize the uniform buffer writer.
490 let Some(mut writer) =
491 light_probes_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
492 else {
493 return;
494 };
495
496 // Process each view.
497 for view_entity in views.iter() {
498 let Ok((render_view_environment_maps, render_view_irradiance_volumes)) =
499 view_light_probes_query.get_mut(view_entity)
500 else {
501 error!("Failed to find `RenderViewLightProbes` for the view!");
502 continue;
503 };
504
505 // Initialize the uniform with only the view environment map, if there
506 // is one.
507 let maybe_view_light_probe_info =
508 render_view_environment_maps.and_then(|maps| maps.view_light_probe_info.as_ref());
509 let mut light_probes_uniform = LightProbesUniform {
510 reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
511 irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
512 reflection_probe_count: render_view_environment_maps
513 .map(RenderViewLightProbes::len)
514 .unwrap_or_default()
515 .min(MAX_VIEW_LIGHT_PROBES) as i32,
516 irradiance_volume_count: render_view_irradiance_volumes
517 .map(RenderViewLightProbes::len)
518 .unwrap_or_default()
519 .min(MAX_VIEW_LIGHT_PROBES) as i32,
520 view_cubemap_index: match maybe_view_light_probe_info {
521 Some(view_light_probe_info) => view_light_probe_info.cubemap_index,
522 None => -1,
523 },
524 smallest_specular_mip_level_for_view: match maybe_view_light_probe_info {
525 Some(view_light_probe_info) => view_light_probe_info.smallest_specular_mip_level,
526 None => 0,
527 },
528 intensity_for_view: match maybe_view_light_probe_info {
529 Some(view_light_probe_info) => view_light_probe_info.intensity,
530 None => 1.0,
531 },
532 view_environment_map_affects_lightmapped_mesh_diffuse: match maybe_view_light_probe_info
533 {
534 Some(view_light_probe_info) => {
535 view_light_probe_info.affects_lightmapped_mesh_diffuse as u32
536 }
537 None => 1,
538 },
539 view_rotation: match maybe_view_light_probe_info {
540 Some(view_light_probe_info) => view_light_probe_info.rotation.inverse().into(),
541 None => Quat::IDENTITY.into(),
542 },
543 };
544
545 // Add any environment maps that [`gather_light_probes`] found to the
546 // uniform.
547 if let Some(render_view_environment_maps) = render_view_environment_maps {
548 render_view_environment_maps.add_to_uniform(
549 &mut light_probes_uniform.reflection_probes,
550 &mut light_probes_uniform.reflection_probe_count,
551 );
552 }
553
554 // Add any irradiance volumes that [`gather_light_probes`] found to the
555 // uniform.
556 if let Some(render_view_irradiance_volumes) = render_view_irradiance_volumes {
557 render_view_irradiance_volumes.add_to_uniform(
558 &mut light_probes_uniform.irradiance_volumes,
559 &mut light_probes_uniform.irradiance_volume_count,
560 );
561 }
562
563 // Queue the view's uniforms to be written to the GPU.
564 let uniform_offset = writer.write(&light_probes_uniform);
565
566 commands
567 .entity(view_entity)
568 .insert(ViewLightProbesUniformOffset(uniform_offset));
569 }
570}
571
572impl Default for LightProbesUniform {
573 fn default() -> Self {
574 Self {
575 reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
576 irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
577 reflection_probe_count: 0,
578 irradiance_volume_count: 0,
579 view_cubemap_index: -1,
580 smallest_specular_mip_level_for_view: 0,
581 intensity_for_view: 1.0,
582 view_environment_map_affects_lightmapped_mesh_diffuse: 1,
583 view_rotation: Quat::IDENTITY.into(),
584 }
585 }
586}
587
588impl<C> LightProbeInfo<C>
589where
590 C: LightProbeComponent,
591{
592 /// Given the set of light probe components, constructs and returns
593 /// [`LightProbeInfo`]. This is done for every light probe in the scene
594 /// every frame.
595 fn new(
596 (main_entity, light_probe_transform, light_probe, environment_map, query_components): (
597 Entity,
598 &GlobalTransform,
599 &LightProbe,
600 &C,
601 <C::QueryData as QueryData>::Item<'_, '_>,
602 ),
603 image_assets: &RenderAssets<GpuImage>,
604 ) -> Option<LightProbeInfo<C>> {
605 let world_from_light =
606 environment_map.get_world_from_light_matrix(&light_probe_transform.affine());
607 let light_from_world_transposed = Mat4::from(world_from_light.inverse()).transpose();
608 let bounding_sphere_radius = (world_from_light.matrix3 * Vec3::ONE).length();
609 environment_map.id(image_assets).map(|id| LightProbeInfo {
610 main_entity: main_entity.into(),
611 world_from_light,
612 light_from_world: [
613 light_from_world_transposed.x_axis,
614 light_from_world_transposed.y_axis,
615 light_from_world_transposed.z_axis,
616 ],
617 bounding_sphere_radius,
618 falloff: light_probe.falloff,
619 parallax_correction_bounds: environment_map
620 .parallax_correction_bounds(&query_components),
621 asset_id: id,
622 intensity: environment_map.intensity(),
623 flags: environment_map.flags(&query_components),
624 })
625 }
626
627 /// Returns the squared distance from this light probe to the camera,
628 /// suitable for distance sorting.
629 fn camera_distance_sort_key(&self, view_transform: &GlobalTransform) -> FloatOrd {
630 FloatOrd(
631 (self.world_from_light.translation - view_transform.translation_vec3a())
632 .length_squared(),
633 )
634 }
635}
636
637impl<C> RenderViewLightProbes<C>
638where
639 C: LightProbeComponent,
640{
641 /// Creates a new empty list of light probes.
642 fn new() -> RenderViewLightProbes<C> {
643 RenderViewLightProbes {
644 binding_index_to_textures: vec![],
645 cubemap_to_binding_index: HashMap::default(),
646 main_entity_to_render_light_probe_index: MainEntityHashMap::default(),
647 render_light_probes: vec![],
648 view_light_probe_info: None,
649 }
650 }
651
652 /// Returns true if there are no light probes in the list.
653 pub(crate) fn is_empty(&self) -> bool {
654 self.render_light_probes.is_empty() && self.view_light_probe_info.is_none()
655 }
656
657 /// Returns the number of light probes in the list.
658 pub(crate) fn len(&self) -> usize {
659 self.render_light_probes.len()
660 }
661
662 /// Adds a cubemap to the list of bindings, if it wasn't there already, and
663 /// returns its index within that list.
664 pub(crate) fn get_or_insert_cubemap(&mut self, cubemap_id: &C::AssetId) -> u32 {
665 *self
666 .cubemap_to_binding_index
667 .entry((*cubemap_id).clone())
668 .or_insert_with(|| {
669 let index = self.binding_index_to_textures.len() as u32;
670 self.binding_index_to_textures.push((*cubemap_id).clone());
671 index
672 })
673 }
674
675 /// Adds all the light probes in this structure to the supplied array, which
676 /// is expected to be shipped to the GPU.
677 fn add_to_uniform(
678 &self,
679 render_light_probes: &mut [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
680 render_light_probe_count: &mut i32,
681 ) {
682 render_light_probes[0..self.render_light_probes.len()]
683 .copy_from_slice(&self.render_light_probes[..]);
684 *render_light_probe_count = self.render_light_probes.len() as i32;
685 }
686
687 /// Gathers up all light probes of the given type in the scene and records
688 /// them in this structure.
689 fn maybe_gather_light_probes(&mut self, light_probes: &[LightProbeInfo<C>]) {
690 for light_probe in light_probes.iter().take(MAX_VIEW_LIGHT_PROBES) {
691 // Determine the index of the cubemap in the binding array.
692 let cubemap_index = self.get_or_insert_cubemap(&light_probe.asset_id);
693
694 // Assign an ID, and write in the index.
695 let render_light_probe_index = self.render_light_probes.len() as u32;
696 debug_assert!((render_light_probe_index as usize) < MAX_VIEW_LIGHT_PROBES);
697 self.main_entity_to_render_light_probe_index
698 .insert(light_probe.main_entity, render_light_probe_index);
699
700 // Write in the light probe data.
701 self.render_light_probes.push(RenderLightProbe {
702 light_from_world_transposed: light_probe.light_from_world,
703 falloff: light_probe.falloff,
704 parallax_correction_bounds: light_probe.parallax_correction_bounds,
705 world_position: light_probe.world_from_light.translation.into(),
706 bounding_sphere_radius: light_probe.bounding_sphere_radius,
707 texture_index: cubemap_index as i32,
708 intensity: light_probe.intensity,
709 flags: light_probe.flags.bits() as u32,
710 });
711 }
712 }
713}
714
715impl<C> Clone for LightProbeInfo<C>
716where
717 C: LightProbeComponent,
718{
719 fn clone(&self) -> Self {
720 Self {
721 main_entity: self.main_entity,
722 light_from_world: self.light_from_world,
723 world_from_light: self.world_from_light,
724 falloff: self.falloff,
725 parallax_correction_bounds: self.parallax_correction_bounds,
726 bounding_sphere_radius: self.bounding_sphere_radius,
727 intensity: self.intensity,
728 flags: self.flags,
729 asset_id: self.asset_id.clone(),
730 }
731 }
732}
733
734/// Adds a diffuse or specular texture view to the `texture_views` list, and
735/// populates `sampler` if this is the first such view.
736pub(crate) fn add_cubemap_texture_view<'a>(
737 texture_views: &mut Vec<&'a <TextureView as Deref>::Target>,
738 sampler: &mut Option<&'a Sampler>,
739 image_id: AssetId<Image>,
740 images: &'a RenderAssets<GpuImage>,
741 fallback_image: &'a FallbackImage,
742) {
743 match images.get(image_id) {
744 None => {
745 // Use the fallback image if the cubemap isn't loaded yet.
746 texture_views.push(&*fallback_image.cube.texture_view);
747 }
748 Some(image) => {
749 // If this is the first texture view, populate `sampler`.
750 if sampler.is_none() {
751 *sampler = Some(&image.sampler);
752 }
753
754 texture_views.push(&*image.texture_view);
755 }
756 }
757}
758
759/// Many things can go wrong when attempting to use texture binding arrays
760/// (a.k.a. bindless textures). This function checks for these pitfalls:
761///
762/// 1. If there aren't enough texture bindings available to accommodate all the
763/// binding arrays, the driver will panic. So we also bail out if there aren't
764/// enough texture bindings available in the fragment shader.
765///
766/// 2. If binding arrays aren't supported on the hardware, then we obviously
767/// can't use them. Adreno <= 610 claims to support bindless, but seems to be
768/// too buggy to be usable.
769///
770/// 3. If binding arrays are supported on the hardware, but they can only be
771/// accessed by uniform indices, that's not good enough, and we bail out.
772///
773/// If binding arrays aren't usable, we disable reflection probes and limit the
774/// number of irradiance volumes in the scene to 1.
775pub(crate) fn binding_arrays_are_usable(
776 render_device: &RenderDevice,
777 render_adapter: &RenderAdapter,
778) -> bool {
779 let adapter_info = RenderAdapterInfo::new(render_adapter.get_info());
780
781 bevy_render::get_adreno_model(&adapter_info).is_none_or(|model| model > 610)
782 && render_device
783 .limits()
784 .max_binding_array_elements_per_shader_stage
785 >= (STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS + MAX_VIEW_LIGHT_PROBES)
786 as u32
787 && render_device
788 .limits()
789 .max_binding_array_sampler_elements_per_shader_stage
790 >= (STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS + MAX_VIEW_LIGHT_PROBES)
791 as u32
792 && render_device.features().contains(
793 WgpuFeatures::TEXTURE_BINDING_ARRAY
794 | WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
795 )
796}