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