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