bevy_pbr/light_probe/mod.rs
1//! Light probes for baked global illumination.
2
3use bevy_app::{App, Plugin};
4use bevy_asset::{load_internal_asset, AssetId, Handle};
5use bevy_core_pipeline::core_3d::Camera3d;
6use bevy_derive::{Deref, DerefMut};
7use bevy_ecs::{
8 component::Component,
9 entity::Entity,
10 query::With,
11 reflect::ReflectComponent,
12 schedule::IntoSystemConfigs,
13 system::{Commands, Local, Query, Res, ResMut, Resource},
14};
15use bevy_image::Image;
16use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3A, Vec4};
17use bevy_reflect::{std_traits::ReflectDefault, Reflect};
18use bevy_render::{
19 extract_instances::ExtractInstancesPlugin,
20 primitives::{Aabb, Frustum},
21 render_asset::RenderAssets,
22 render_resource::{DynamicUniformBuffer, Sampler, Shader, ShaderType, TextureView},
23 renderer::{RenderDevice, RenderQueue},
24 settings::WgpuFeatures,
25 sync_world::RenderEntity,
26 texture::{FallbackImage, GpuImage},
27 view::{ExtractedView, Visibility},
28 Extract, ExtractSchedule, Render, RenderApp, RenderSet,
29};
30use bevy_transform::{components::Transform, prelude::GlobalTransform};
31use bevy_utils::{tracing::error, HashMap};
32
33use core::{hash::Hash, ops::Deref};
34
35use crate::{
36 irradiance_volume::IRRADIANCE_VOLUME_SHADER_HANDLE,
37 light_probe::environment_map::{
38 EnvironmentMapIds, EnvironmentMapLight, ENVIRONMENT_MAP_SHADER_HANDLE,
39 },
40};
41
42use self::irradiance_volume::IrradianceVolume;
43
44pub const LIGHT_PROBE_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(8954249792581071582);
45
46pub mod environment_map;
47pub mod irradiance_volume;
48
49/// The maximum number of each type of light probe that each view will consider.
50///
51/// Because the fragment shader does a linear search through the list for each
52/// fragment, this number needs to be relatively small.
53pub const MAX_VIEW_LIGHT_PROBES: usize = 8;
54
55/// How many texture bindings are used in the fragment shader, *not* counting
56/// environment maps or irradiance volumes.
57const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;
58
59/// Adds support for light probes: cuboid bounding regions that apply global
60/// illumination to objects within them.
61///
62/// This also adds support for view environment maps: diffuse and specular
63/// cubemaps applied to all objects that a view renders.
64pub struct LightProbePlugin;
65
66/// A marker component for a light probe, which is a cuboid region that provides
67/// global illumination to all fragments inside it.
68///
69/// Note that a light probe will have no effect unless the entity contains some
70/// kind of illumination, which can either be an [`EnvironmentMapLight`] or an
71/// [`IrradianceVolume`].
72///
73/// The light probe range is conceptually a unit cube (1×1×1) centered on the
74/// origin. The [`Transform`] applied to this entity can scale, rotate, or translate
75/// that cube so that it contains all fragments that should take this light probe into account.
76///
77/// When multiple sources of indirect illumination can be applied to a fragment,
78/// the highest-quality one is chosen. Diffuse and specular illumination are
79/// considered separately, so, for example, Bevy may decide to sample the
80/// diffuse illumination from an irradiance volume and the specular illumination
81/// from a reflection probe. From highest priority to lowest priority, the
82/// ranking is as follows:
83///
84/// | Rank | Diffuse | Specular |
85/// | ---- | -------------------- | -------------------- |
86/// | 1 | Lightmap | Lightmap |
87/// | 2 | Irradiance volume | Reflection probe |
88/// | 3 | Reflection probe | View environment map |
89/// | 4 | View environment map | |
90///
91/// Note that ambient light is always added to the diffuse component and does
92/// not participate in the ranking. That is, ambient light is applied in
93/// addition to, not instead of, the light sources above.
94///
95/// A terminology note: Unfortunately, there is little agreement across game and
96/// graphics engines as to what to call the various techniques that Bevy groups
97/// under the term *light probe*. In Bevy, a *light probe* is the generic term
98/// that encompasses both *reflection probes* and *irradiance volumes*. In
99/// object-oriented terms, *light probe* is the superclass, and *reflection
100/// probe* and *irradiance volume* are subclasses. In other engines, you may see
101/// the term *light probe* refer to an irradiance volume with a single voxel, or
102/// perhaps some other technique, while in Bevy *light probe* refers not to a
103/// specific technique but rather to a class of techniques. Developers familiar
104/// with other engines should be aware of this terminology difference.
105#[derive(Component, Debug, Clone, Copy, Default, Reflect)]
106#[reflect(Component, Default, Debug)]
107#[require(Transform, Visibility)]
108pub struct LightProbe;
109
110/// A GPU type that stores information about a light probe.
111#[derive(Clone, Copy, ShaderType, Default)]
112struct RenderLightProbe {
113 /// The transform from the world space to the model space. This is used to
114 /// efficiently check for bounding box intersection.
115 light_from_world_transposed: [Vec4; 3],
116
117 /// The index of the texture or textures in the appropriate binding array or
118 /// arrays.
119 ///
120 /// For example, for reflection probes this is the index of the cubemap in
121 /// the diffuse and specular texture arrays.
122 texture_index: i32,
123
124 /// Scale factor applied to the light generated by this light probe.
125 ///
126 /// See the comment in [`EnvironmentMapLight`] for details.
127 intensity: f32,
128}
129
130/// A per-view shader uniform that specifies all the light probes that the view
131/// takes into account.
132#[derive(ShaderType)]
133pub struct LightProbesUniform {
134 /// The list of applicable reflection probes, sorted from nearest to the
135 /// camera to the farthest away from the camera.
136 reflection_probes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
137
138 /// The list of applicable irradiance volumes, sorted from nearest to the
139 /// camera to the farthest away from the camera.
140 irradiance_volumes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
141
142 /// The number of reflection probes in the list.
143 reflection_probe_count: i32,
144
145 /// The number of irradiance volumes in the list.
146 irradiance_volume_count: i32,
147
148 /// The index of the diffuse and specular environment maps associated with
149 /// the view itself. This is used as a fallback if no reflection probe in
150 /// the list contains the fragment.
151 view_cubemap_index: i32,
152
153 /// The smallest valid mipmap level for the specular environment cubemap
154 /// associated with the view.
155 smallest_specular_mip_level_for_view: u32,
156
157 /// The intensity of the environment cubemap associated with the view.
158 ///
159 /// See the comment in [`EnvironmentMapLight`] for details.
160 intensity_for_view: f32,
161}
162
163/// A GPU buffer that stores information about all light probes.
164#[derive(Resource, Default, Deref, DerefMut)]
165pub struct LightProbesBuffer(DynamicUniformBuffer<LightProbesUniform>);
166
167/// A component attached to each camera in the render world that stores the
168/// index of the [`LightProbesUniform`] in the [`LightProbesBuffer`].
169#[derive(Component, Default, Deref, DerefMut)]
170pub struct ViewLightProbesUniformOffset(u32);
171
172/// Information that [`gather_light_probes`] keeps about each light probe.
173///
174/// This information is parameterized by the [`LightProbeComponent`] type. This
175/// will either be [`EnvironmentMapLight`] for reflection probes or
176/// [`IrradianceVolume`] for irradiance volumes.
177#[allow(dead_code)]
178struct LightProbeInfo<C>
179where
180 C: LightProbeComponent,
181{
182 // The transform from world space to light probe space.
183 light_from_world: Mat4,
184
185 // The transform from light probe space to world space.
186 world_from_light: Affine3A,
187
188 // Scale factor applied to the diffuse and specular light generated by this
189 // reflection probe.
190 //
191 // See the comment in [`EnvironmentMapLight`] for details.
192 intensity: f32,
193
194 // The IDs of all assets associated with this light probe.
195 //
196 // Because each type of light probe component may reference different types
197 // of assets (e.g. a reflection probe references two cubemap assets while an
198 // irradiance volume references a single 3D texture asset), this is generic.
199 asset_id: C::AssetId,
200}
201
202/// A component, part of the render world, that stores the mapping from asset ID
203/// or IDs to the texture index in the appropriate binding arrays.
204///
205/// Cubemap textures belonging to environment maps are collected into binding
206/// arrays, and the index of each texture is presented to the shader for runtime
207/// lookup. 3D textures belonging to reflection probes are likewise collected
208/// into binding arrays, and the shader accesses the 3D texture by index.
209///
210/// This component is attached to each view in the render world, because each
211/// view may have a different set of light probes that it considers and therefore
212/// the texture indices are per-view.
213#[derive(Component, Default)]
214pub struct RenderViewLightProbes<C>
215where
216 C: LightProbeComponent,
217{
218 /// The list of environment maps presented to the shader, in order.
219 binding_index_to_textures: Vec<C::AssetId>,
220
221 /// The reverse of `binding_index_to_cubemap`: a map from the texture ID to
222 /// the index in `binding_index_to_cubemap`.
223 cubemap_to_binding_index: HashMap<C::AssetId, u32>,
224
225 /// Information about each light probe, ready for upload to the GPU, sorted
226 /// in order from closest to the camera to farthest.
227 ///
228 /// Note that this is not necessarily ordered by binding index. So don't
229 /// write code like
230 /// `render_light_probes[cubemap_to_binding_index[asset_id]]`; instead
231 /// search for the light probe with the appropriate binding index in this
232 /// array.
233 render_light_probes: Vec<RenderLightProbe>,
234
235 /// Information needed to render the light probe attached directly to the
236 /// view, if applicable.
237 ///
238 /// A light probe attached directly to a view represents a "global" light
239 /// probe that affects all objects not in the bounding region of any light
240 /// probe. Currently, the only light probe type that supports this is the
241 /// [`EnvironmentMapLight`].
242 view_light_probe_info: C::ViewLightProbeInfo,
243}
244
245/// A trait implemented by all components that represent light probes.
246///
247/// Currently, the two light probe types are [`EnvironmentMapLight`] and
248/// [`IrradianceVolume`], for reflection probes and irradiance volumes
249/// respectively.
250///
251/// Most light probe systems are written to be generic over the type of light
252/// probe. This allows much of the code to be shared and enables easy addition
253/// of more light probe types (e.g. real-time reflection planes) in the future.
254pub trait LightProbeComponent: Send + Sync + Component + Sized {
255 /// Holds [`AssetId`]s of the texture or textures that this light probe
256 /// references.
257 ///
258 /// This can just be [`AssetId`] if the light probe only references one
259 /// texture. If it references multiple textures, it will be a structure
260 /// containing those asset IDs.
261 type AssetId: Send + Sync + Clone + Eq + Hash;
262
263 /// If the light probe can be attached to the view itself (as opposed to a
264 /// cuboid region within the scene), this contains the information that will
265 /// be passed to the GPU in order to render it. Otherwise, this will be
266 /// `()`.
267 ///
268 /// Currently, only reflection probes (i.e. [`EnvironmentMapLight`]) can be
269 /// attached directly to views.
270 type ViewLightProbeInfo: Send + Sync + Default;
271
272 /// Returns the asset ID or asset IDs of the texture or textures referenced
273 /// by this light probe.
274 fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId>;
275
276 /// Returns the intensity of this light probe.
277 ///
278 /// This is a scaling factor that will be multiplied by the value or values
279 /// sampled from the texture.
280 fn intensity(&self) -> f32;
281
282 /// Creates an instance of [`RenderViewLightProbes`] containing all the
283 /// information needed to render this light probe.
284 ///
285 /// This is called for every light probe in view every frame.
286 fn create_render_view_light_probes(
287 view_component: Option<&Self>,
288 image_assets: &RenderAssets<GpuImage>,
289 ) -> RenderViewLightProbes<Self>;
290}
291
292impl LightProbe {
293 /// Creates a new light probe component.
294 #[inline]
295 pub fn new() -> Self {
296 Self
297 }
298}
299
300/// The uniform struct extracted from [`EnvironmentMapLight`].
301/// Will be available for use in the Environment Map shader.
302#[derive(Component, ShaderType, Clone)]
303pub struct EnvironmentMapUniform {
304 /// The world space transformation matrix of the sample ray for environment cubemaps.
305 transform: Mat4,
306}
307
308impl Default for EnvironmentMapUniform {
309 fn default() -> Self {
310 EnvironmentMapUniform {
311 transform: Mat4::IDENTITY,
312 }
313 }
314}
315
316/// A GPU buffer that stores the environment map settings for each view.
317#[derive(Resource, Default, Deref, DerefMut)]
318pub struct EnvironmentMapUniformBuffer(pub DynamicUniformBuffer<EnvironmentMapUniform>);
319
320/// A component that stores the offset within the
321/// [`EnvironmentMapUniformBuffer`] for each view.
322#[derive(Component, Default, Deref, DerefMut)]
323pub struct ViewEnvironmentMapUniformOffset(u32);
324
325impl Plugin for LightProbePlugin {
326 fn build(&self, app: &mut App) {
327 load_internal_asset!(
328 app,
329 LIGHT_PROBE_SHADER_HANDLE,
330 "light_probe.wgsl",
331 Shader::from_wgsl
332 );
333 load_internal_asset!(
334 app,
335 ENVIRONMENT_MAP_SHADER_HANDLE,
336 "environment_map.wgsl",
337 Shader::from_wgsl
338 );
339 load_internal_asset!(
340 app,
341 IRRADIANCE_VOLUME_SHADER_HANDLE,
342 "irradiance_volume.wgsl",
343 Shader::from_wgsl
344 );
345
346 app.register_type::<LightProbe>()
347 .register_type::<EnvironmentMapLight>()
348 .register_type::<IrradianceVolume>();
349 }
350
351 fn finish(&self, app: &mut App) {
352 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
353 return;
354 };
355
356 render_app
357 .add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new())
358 .init_resource::<LightProbesBuffer>()
359 .init_resource::<EnvironmentMapUniformBuffer>()
360 .add_systems(ExtractSchedule, gather_environment_map_uniform)
361 .add_systems(ExtractSchedule, gather_light_probes::<EnvironmentMapLight>)
362 .add_systems(ExtractSchedule, gather_light_probes::<IrradianceVolume>)
363 .add_systems(
364 Render,
365 (upload_light_probes, prepare_environment_uniform_buffer)
366 .in_set(RenderSet::PrepareResources),
367 );
368 }
369}
370
371/// Extracts [`EnvironmentMapLight`] from views and creates [`EnvironmentMapUniform`] for them.
372///
373/// Compared to the `ExtractComponentPlugin`, this implementation will create a default instance
374/// if one does not already exist.
375fn gather_environment_map_uniform(
376 view_query: Extract<Query<(RenderEntity, Option<&EnvironmentMapLight>), With<Camera3d>>>,
377 mut commands: Commands,
378) {
379 for (view_entity, environment_map_light) in view_query.iter() {
380 let environment_map_uniform = if let Some(environment_map_light) = environment_map_light {
381 EnvironmentMapUniform {
382 transform: Transform::from_rotation(environment_map_light.rotation)
383 .compute_matrix()
384 .inverse(),
385 }
386 } else {
387 EnvironmentMapUniform::default()
388 };
389 commands
390 .get_entity(view_entity)
391 .expect("Environment map light entity wasn't synced.")
392 .insert(environment_map_uniform);
393 }
394}
395
396/// Gathers up all light probes of a single type in the scene and assigns them
397/// to views, performing frustum culling and distance sorting in the process.
398fn gather_light_probes<C>(
399 image_assets: Res<RenderAssets<GpuImage>>,
400 light_probe_query: Extract<Query<(&GlobalTransform, &C), With<LightProbe>>>,
401 view_query: Extract<
402 Query<(RenderEntity, &GlobalTransform, &Frustum, Option<&C>), With<Camera3d>>,
403 >,
404 mut reflection_probes: Local<Vec<LightProbeInfo<C>>>,
405 mut view_reflection_probes: Local<Vec<LightProbeInfo<C>>>,
406 mut commands: Commands,
407) where
408 C: LightProbeComponent,
409{
410 // Create [`LightProbeInfo`] for every light probe in the scene.
411 reflection_probes.clear();
412 reflection_probes.extend(
413 light_probe_query
414 .iter()
415 .filter_map(|query_row| LightProbeInfo::new(query_row, &image_assets)),
416 );
417
418 // Build up the light probes uniform and the key table.
419 for (view_entity, view_transform, view_frustum, view_component) in view_query.iter() {
420 // Cull light probes outside the view frustum.
421 view_reflection_probes.clear();
422 view_reflection_probes.extend(
423 reflection_probes
424 .iter()
425 .filter(|light_probe_info| light_probe_info.frustum_cull(view_frustum))
426 .cloned(),
427 );
428
429 // Sort by distance to camera.
430 view_reflection_probes.sort_by_cached_key(|light_probe_info| {
431 light_probe_info.camera_distance_sort_key(view_transform)
432 });
433
434 // Create the light probes list.
435 let mut render_view_light_probes =
436 C::create_render_view_light_probes(view_component, &image_assets);
437
438 // Gather up the light probes in the list.
439 render_view_light_probes.maybe_gather_light_probes(&view_reflection_probes);
440
441 // Record the per-view light probes.
442 if render_view_light_probes.is_empty() {
443 commands
444 .get_entity(view_entity)
445 .expect("View entity wasn't synced.")
446 .remove::<RenderViewLightProbes<C>>();
447 } else {
448 commands
449 .get_entity(view_entity)
450 .expect("View entity wasn't synced.")
451 .insert(render_view_light_probes);
452 }
453 }
454}
455
456/// Gathers up environment map settings for each applicable view and
457/// writes them into a GPU buffer.
458pub fn prepare_environment_uniform_buffer(
459 mut commands: Commands,
460 views: Query<(Entity, Option<&EnvironmentMapUniform>), With<ExtractedView>>,
461 mut environment_uniform_buffer: ResMut<EnvironmentMapUniformBuffer>,
462 render_device: Res<RenderDevice>,
463 render_queue: Res<RenderQueue>,
464) {
465 let Some(mut writer) =
466 environment_uniform_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
467 else {
468 return;
469 };
470
471 for (view, environment_uniform) in views.iter() {
472 let uniform_offset = match environment_uniform {
473 None => 0,
474 Some(environment_uniform) => writer.write(environment_uniform),
475 };
476 commands
477 .entity(view)
478 .insert(ViewEnvironmentMapUniformOffset(uniform_offset));
479 }
480}
481
482// A system that runs after [`gather_light_probes`] and populates the GPU
483// uniforms with the results.
484//
485// Note that, unlike [`gather_light_probes`], this system is not generic over
486// the type of light probe. It collects light probes of all types together into
487// a single structure, ready to be passed to the shader.
488fn upload_light_probes(
489 mut commands: Commands,
490 views: Query<Entity, With<ExtractedView>>,
491 mut light_probes_buffer: ResMut<LightProbesBuffer>,
492 mut view_light_probes_query: Query<(
493 Option<&RenderViewLightProbes<EnvironmentMapLight>>,
494 Option<&RenderViewLightProbes<IrradianceVolume>>,
495 )>,
496 render_device: Res<RenderDevice>,
497 render_queue: Res<RenderQueue>,
498) {
499 // If there are no views, bail.
500 if views.is_empty() {
501 return;
502 }
503
504 // Initialize the uniform buffer writer.
505 let mut writer = light_probes_buffer
506 .get_writer(views.iter().len(), &render_device, &render_queue)
507 .unwrap();
508
509 // Process each view.
510 for view_entity in views.iter() {
511 let Ok((render_view_environment_maps, render_view_irradiance_volumes)) =
512 view_light_probes_query.get_mut(view_entity)
513 else {
514 error!("Failed to find `RenderViewLightProbes` for the view!");
515 continue;
516 };
517
518 // Initialize the uniform with only the view environment map, if there
519 // is one.
520 let mut light_probes_uniform = LightProbesUniform {
521 reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
522 irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
523 reflection_probe_count: render_view_environment_maps
524 .map(RenderViewLightProbes::len)
525 .unwrap_or_default()
526 .min(MAX_VIEW_LIGHT_PROBES) as i32,
527 irradiance_volume_count: render_view_irradiance_volumes
528 .map(RenderViewLightProbes::len)
529 .unwrap_or_default()
530 .min(MAX_VIEW_LIGHT_PROBES) as i32,
531 view_cubemap_index: render_view_environment_maps
532 .map(|maps| maps.view_light_probe_info.cubemap_index)
533 .unwrap_or(-1),
534 smallest_specular_mip_level_for_view: render_view_environment_maps
535 .map(|maps| maps.view_light_probe_info.smallest_specular_mip_level)
536 .unwrap_or(0),
537 intensity_for_view: render_view_environment_maps
538 .map(|maps| maps.view_light_probe_info.intensity)
539 .unwrap_or(1.0),
540 };
541
542 // Add any environment maps that [`gather_light_probes`] found to the
543 // uniform.
544 if let Some(render_view_environment_maps) = render_view_environment_maps {
545 render_view_environment_maps.add_to_uniform(
546 &mut light_probes_uniform.reflection_probes,
547 &mut light_probes_uniform.reflection_probe_count,
548 );
549 }
550
551 // Add any irradiance volumes that [`gather_light_probes`] found to the
552 // uniform.
553 if let Some(render_view_irradiance_volumes) = render_view_irradiance_volumes {
554 render_view_irradiance_volumes.add_to_uniform(
555 &mut light_probes_uniform.irradiance_volumes,
556 &mut light_probes_uniform.irradiance_volume_count,
557 );
558 }
559
560 // Queue the view's uniforms to be written to the GPU.
561 let uniform_offset = writer.write(&light_probes_uniform);
562
563 commands
564 .entity(view_entity)
565 .insert(ViewLightProbesUniformOffset(uniform_offset));
566 }
567}
568
569impl Default for LightProbesUniform {
570 fn default() -> Self {
571 Self {
572 reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
573 irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
574 reflection_probe_count: 0,
575 irradiance_volume_count: 0,
576 view_cubemap_index: -1,
577 smallest_specular_mip_level_for_view: 0,
578 intensity_for_view: 1.0,
579 }
580 }
581}
582
583impl<C> LightProbeInfo<C>
584where
585 C: LightProbeComponent,
586{
587 /// Given the set of light probe components, constructs and returns
588 /// [`LightProbeInfo`]. This is done for every light probe in the scene
589 /// every frame.
590 fn new(
591 (light_probe_transform, environment_map): (&GlobalTransform, &C),
592 image_assets: &RenderAssets<GpuImage>,
593 ) -> Option<LightProbeInfo<C>> {
594 environment_map.id(image_assets).map(|id| LightProbeInfo {
595 world_from_light: light_probe_transform.affine(),
596 light_from_world: light_probe_transform.compute_matrix().inverse(),
597 asset_id: id,
598 intensity: environment_map.intensity(),
599 })
600 }
601
602 /// Returns true if this light probe is in the viewing frustum of the camera
603 /// or false if it isn't.
604 fn frustum_cull(&self, view_frustum: &Frustum) -> bool {
605 view_frustum.intersects_obb(
606 &Aabb {
607 center: Vec3A::default(),
608 half_extents: Vec3A::splat(0.5),
609 },
610 &self.world_from_light,
611 true,
612 false,
613 )
614 }
615
616 /// Returns the squared distance from this light probe to the camera,
617 /// suitable for distance sorting.
618 fn camera_distance_sort_key(&self, view_transform: &GlobalTransform) -> FloatOrd {
619 FloatOrd(
620 (self.world_from_light.translation - view_transform.translation_vec3a())
621 .length_squared(),
622 )
623 }
624}
625
626impl<C> RenderViewLightProbes<C>
627where
628 C: LightProbeComponent,
629{
630 /// Creates a new empty list of light probes.
631 fn new() -> RenderViewLightProbes<C> {
632 RenderViewLightProbes {
633 binding_index_to_textures: vec![],
634 cubemap_to_binding_index: HashMap::new(),
635 render_light_probes: vec![],
636 view_light_probe_info: C::ViewLightProbeInfo::default(),
637 }
638 }
639
640 /// Returns true if there are no light probes in the list.
641 pub(crate) fn is_empty(&self) -> bool {
642 self.binding_index_to_textures.is_empty()
643 }
644
645 /// Returns the number of light probes in the list.
646 pub(crate) fn len(&self) -> usize {
647 self.binding_index_to_textures.len()
648 }
649
650 /// Adds a cubemap to the list of bindings, if it wasn't there already, and
651 /// returns its index within that list.
652 pub(crate) fn get_or_insert_cubemap(&mut self, cubemap_id: &C::AssetId) -> u32 {
653 *self
654 .cubemap_to_binding_index
655 .entry((*cubemap_id).clone())
656 .or_insert_with(|| {
657 let index = self.binding_index_to_textures.len() as u32;
658 self.binding_index_to_textures.push((*cubemap_id).clone());
659 index
660 })
661 }
662
663 /// Adds all the light probes in this structure to the supplied array, which
664 /// is expected to be shipped to the GPU.
665 fn add_to_uniform(
666 &self,
667 render_light_probes: &mut [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
668 render_light_probe_count: &mut i32,
669 ) {
670 render_light_probes[0..self.render_light_probes.len()]
671 .copy_from_slice(&self.render_light_probes[..]);
672 *render_light_probe_count = self.render_light_probes.len() as i32;
673 }
674
675 /// Gathers up all light probes of the given type in the scene and records
676 /// them in this structure.
677 fn maybe_gather_light_probes(&mut self, light_probes: &[LightProbeInfo<C>]) {
678 for light_probe in light_probes.iter().take(MAX_VIEW_LIGHT_PROBES) {
679 // Determine the index of the cubemap in the binding array.
680 let cubemap_index = self.get_or_insert_cubemap(&light_probe.asset_id);
681
682 // Transpose the inverse transform to compress the structure on the
683 // GPU (from 4 `Vec4`s to 3 `Vec4`s). The shader will transpose it
684 // to recover the original inverse transform.
685 let light_from_world_transposed = light_probe.light_from_world.transpose();
686
687 // Write in the light probe data.
688 self.render_light_probes.push(RenderLightProbe {
689 light_from_world_transposed: [
690 light_from_world_transposed.x_axis,
691 light_from_world_transposed.y_axis,
692 light_from_world_transposed.z_axis,
693 ],
694 texture_index: cubemap_index as i32,
695 intensity: light_probe.intensity,
696 });
697 }
698 }
699}
700
701impl<C> Clone for LightProbeInfo<C>
702where
703 C: LightProbeComponent,
704{
705 fn clone(&self) -> Self {
706 Self {
707 light_from_world: self.light_from_world,
708 world_from_light: self.world_from_light,
709 intensity: self.intensity,
710 asset_id: self.asset_id.clone(),
711 }
712 }
713}
714
715/// Adds a diffuse or specular texture view to the `texture_views` list, and
716/// populates `sampler` if this is the first such view.
717pub(crate) fn add_cubemap_texture_view<'a>(
718 texture_views: &mut Vec<&'a <TextureView as Deref>::Target>,
719 sampler: &mut Option<&'a Sampler>,
720 image_id: AssetId<Image>,
721 images: &'a RenderAssets<GpuImage>,
722 fallback_image: &'a FallbackImage,
723) {
724 match images.get(image_id) {
725 None => {
726 // Use the fallback image if the cubemap isn't loaded yet.
727 texture_views.push(&*fallback_image.cube.texture_view);
728 }
729 Some(image) => {
730 // If this is the first texture view, populate `sampler`.
731 if sampler.is_none() {
732 *sampler = Some(&image.sampler);
733 }
734
735 texture_views.push(&*image.texture_view);
736 }
737 }
738}
739
740/// Many things can go wrong when attempting to use texture binding arrays
741/// (a.k.a. bindless textures). This function checks for these pitfalls:
742///
743/// 1. If GLSL support is enabled at the feature level, then in debug mode
744/// `naga_oil` will attempt to compile all shader modules under GLSL to check
745/// validity of names, even if GLSL isn't actually used. This will cause a crash
746/// if binding arrays are enabled, because binding arrays are currently
747/// unimplemented in the GLSL backend of Naga. Therefore, we disable binding
748/// arrays if the `shader_format_glsl` feature is present.
749///
750/// 2. If there aren't enough texture bindings available to accommodate all the
751/// binding arrays, the driver will panic. So we also bail out if there aren't
752/// enough texture bindings available in the fragment shader.
753///
754/// 3. If binding arrays aren't supported on the hardware, then we obviously
755/// can't use them.
756///
757/// 4. If binding arrays are supported on the hardware, but they can only be
758/// accessed by uniform indices, that's not good enough, and we bail out.
759///
760/// If binding arrays aren't usable, we disable reflection probes and limit the
761/// number of irradiance volumes in the scene to 1.
762pub(crate) fn binding_arrays_are_usable(render_device: &RenderDevice) -> bool {
763 !cfg!(feature = "shader_format_glsl")
764 && render_device.limits().max_storage_textures_per_shader_stage
765 >= (STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS + MAX_VIEW_LIGHT_PROBES)
766 as u32
767 && render_device.features().contains(
768 WgpuFeatures::TEXTURE_BINDING_ARRAY
769 | WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
770 )
771}