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