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}