Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/light_probe/environment_map.rs
9412 views
1
//! Environment maps and reflection probes.
2
//!
3
//! An *environment map* consists of a pair of diffuse and specular cubemaps
4
//! that together reflect the static surrounding area of a region in space. When
5
//! available, the PBR shader uses these to apply diffuse light and calculate
6
//! specular reflections.
7
//!
8
//! Environment maps come in two flavors, depending on what other components the
9
//! entities they're attached to have:
10
//!
11
//! 1. If attached to a view, they represent the objects located a very far
12
//! distance from the view, in a similar manner to a skybox. Essentially, these
13
//! *view environment maps* represent a higher-quality replacement for
14
//! [`AmbientLight`](bevy_light::AmbientLight) for outdoor scenes. The indirect light from such
15
//! environment maps are added to every point of the scene, including
16
//! interior enclosed areas.
17
//!
18
//! 2. If attached to a [`bevy_light::LightProbe`], environment maps represent the immediate
19
//! surroundings of a specific location in the scene. These types of
20
//! environment maps are known as *reflection probes*.
21
//!
22
//! Typically, environment maps are static (i.e. "baked", calculated ahead of
23
//! time) and so only reflect fixed static geometry. The environment maps must
24
//! be pre-filtered into a pair of cubemaps, one for the diffuse component and
25
//! one for the specular component, according to the [split-sum approximation].
26
//! To pre-filter your environment map, you can use the [glTF IBL Sampler] or
27
//! its [artist-friendly UI]. The diffuse map uses the Lambertian distribution,
28
//! while the specular map uses the GGX distribution.
29
//!
30
//! The Khronos Group has [several pre-filtered environment maps] available for
31
//! you to use.
32
//!
33
//! Currently, reflection probes (i.e. environment maps attached to light
34
//! probes) use binding arrays (also known as bindless textures) and
35
//! consequently aren't supported on WebGL2 or WebGPU. Reflection probes are
36
//! also unsupported if GLSL is in use, due to `naga` limitations. Environment
37
//! maps attached to views are, however, supported on all platforms.
38
//!
39
//! [split-sum approximation]: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
40
//!
41
//! [glTF IBL Sampler]: https://github.com/KhronosGroup/glTF-IBL-Sampler
42
//!
43
//! [artist-friendly UI]: https://github.com/pcwalton/gltf-ibl-sampler-egui
44
//!
45
//! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments
46
47
use bevy_asset::AssetId;
48
use bevy_ecs::{
49
query::{QueryData, QueryItem},
50
system::lifetimeless::Read,
51
};
52
use bevy_image::Image;
53
use bevy_light::{EnvironmentMapLight, ParallaxCorrection};
54
use bevy_math::{Affine3A, Vec3};
55
use bevy_render::{
56
extract_instances::ExtractInstance,
57
render_asset::RenderAssets,
58
render_resource::{
59
binding_types::{self, uniform_buffer},
60
BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, ShaderStages, TextureSampleType,
61
TextureView,
62
},
63
renderer::{RenderAdapter, RenderDevice},
64
texture::{FallbackImage, GpuImage},
65
};
66
67
use core::{num::NonZero, ops::Deref};
68
69
use crate::{
70
add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform,
71
RenderLightProbeFlags, MAX_VIEW_LIGHT_PROBES,
72
};
73
74
use super::{LightProbeComponent, RenderViewLightProbes};
75
76
/// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles.
77
///
78
/// This is for use in the render app.
79
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
80
pub struct EnvironmentMapIds {
81
/// The blurry image that represents diffuse radiance surrounding a region.
82
pub diffuse: AssetId<Image>,
83
/// The typically-sharper, mipmapped image that represents specular radiance
84
/// surrounding a region.
85
pub specular: AssetId<Image>,
86
}
87
88
/// All the bind group entries necessary for PBR shaders to access the
89
/// environment maps exposed to a view.
90
pub(crate) enum RenderViewEnvironmentMapBindGroupEntries<'a> {
91
/// The version used when binding arrays aren't available on the current
92
/// platform.
93
Single {
94
/// The texture view of the view's diffuse cubemap.
95
diffuse_texture_view: &'a TextureView,
96
97
/// The texture view of the view's specular cubemap.
98
specular_texture_view: &'a TextureView,
99
100
/// The sampler used to sample elements of both `diffuse_texture_views` and
101
/// `specular_texture_views`.
102
sampler: &'a Sampler,
103
},
104
105
/// The version used when binding arrays are available on the current
106
/// platform.
107
Multiple {
108
/// A texture view of each diffuse cubemap, in the same order that they are
109
/// supplied to the view (i.e. in the same order as
110
/// `binding_index_to_cubemap` in [`RenderViewLightProbes`]).
111
///
112
/// This is a vector of `wgpu::TextureView`s. But we don't want to import
113
/// `wgpu` in this crate, so we refer to it indirectly like this.
114
diffuse_texture_views: Vec<&'a <TextureView as Deref>::Target>,
115
116
/// As above, but for specular cubemaps.
117
specular_texture_views: Vec<&'a <TextureView as Deref>::Target>,
118
119
/// The sampler used to sample elements of both `diffuse_texture_views` and
120
/// `specular_texture_views`.
121
sampler: &'a Sampler,
122
},
123
}
124
125
/// Information about the environment map attached to the view, if any. This is
126
/// a global environment map that lights everything visible in the view, as
127
/// opposed to a light probe which affects only a specific area.
128
pub struct EnvironmentMapViewLightProbeInfo {
129
/// The index of the diffuse and specular cubemaps in the binding arrays.
130
pub(crate) cubemap_index: i32,
131
/// The smallest mip level of the specular cubemap.
132
pub(crate) smallest_specular_mip_level: u32,
133
/// The scale factor applied to the diffuse and specular light in the
134
/// cubemap. This is in units of cd/m² (candela per square meter).
135
pub(crate) intensity: f32,
136
/// Whether this lightmap affects the diffuse lighting of lightmapped
137
/// meshes.
138
pub(crate) affects_lightmapped_mesh_diffuse: bool,
139
}
140
141
impl ExtractInstance for EnvironmentMapIds {
142
type QueryData = Read<EnvironmentMapLight>;
143
144
type QueryFilter = ();
145
146
fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self> {
147
Some(EnvironmentMapIds {
148
diffuse: item.diffuse_map.id(),
149
specular: item.specular_map.id(),
150
})
151
}
152
}
153
154
/// Returns the bind group layout entries for the environment map diffuse and
155
/// specular binding arrays respectively, in addition to the sampler.
156
pub(crate) fn get_bind_group_layout_entries(
157
render_device: &RenderDevice,
158
render_adapter: &RenderAdapter,
159
) -> [BindGroupLayoutEntryBuilder; 4] {
160
let mut texture_cube_binding =
161
binding_types::texture_cube(TextureSampleType::Float { filterable: true });
162
if binding_arrays_are_usable(render_device, render_adapter) {
163
texture_cube_binding =
164
texture_cube_binding.count(NonZero::<u32>::new(MAX_VIEW_LIGHT_PROBES as _).unwrap());
165
}
166
167
[
168
texture_cube_binding,
169
texture_cube_binding,
170
binding_types::sampler(SamplerBindingType::Filtering),
171
uniform_buffer::<EnvironmentMapUniform>(true).visibility(ShaderStages::FRAGMENT),
172
]
173
}
174
175
impl<'a> RenderViewEnvironmentMapBindGroupEntries<'a> {
176
/// Looks up and returns the bindings for the environment map diffuse and
177
/// specular binding arrays respectively, as well as the sampler.
178
pub(crate) fn get(
179
render_view_environment_maps: Option<&RenderViewLightProbes<EnvironmentMapLight>>,
180
images: &'a RenderAssets<GpuImage>,
181
fallback_image: &'a FallbackImage,
182
render_device: &RenderDevice,
183
render_adapter: &RenderAdapter,
184
) -> RenderViewEnvironmentMapBindGroupEntries<'a> {
185
if binding_arrays_are_usable(render_device, render_adapter) {
186
// Initialize the diffuse and specular texture views with the fallback texture.
187
let mut diffuse_texture_views = vec![];
188
let mut specular_texture_views = vec![];
189
let mut sampler = None;
190
191
if let Some(environment_maps) = render_view_environment_maps {
192
for &cubemap_id in &environment_maps.binding_index_to_textures {
193
add_cubemap_texture_view(
194
&mut diffuse_texture_views,
195
&mut sampler,
196
cubemap_id.diffuse,
197
images,
198
fallback_image,
199
);
200
add_cubemap_texture_view(
201
&mut specular_texture_views,
202
&mut sampler,
203
cubemap_id.specular,
204
images,
205
fallback_image,
206
);
207
}
208
}
209
210
// Pad out the bindings to the size of the binding array using fallback
211
// textures. This is necessary on D3D12 and Metal.
212
diffuse_texture_views.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);
213
specular_texture_views
214
.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);
215
216
return RenderViewEnvironmentMapBindGroupEntries::Multiple {
217
diffuse_texture_views,
218
specular_texture_views,
219
sampler: sampler.unwrap_or(&fallback_image.cube.sampler),
220
};
221
}
222
223
if let Some(environment_maps) = render_view_environment_maps
224
&& let Some(cubemap) = environment_maps.binding_index_to_textures.first()
225
&& let (Some(diffuse_image), Some(specular_image)) =
226
(images.get(cubemap.diffuse), images.get(cubemap.specular))
227
{
228
return RenderViewEnvironmentMapBindGroupEntries::Single {
229
diffuse_texture_view: &diffuse_image.texture_view,
230
specular_texture_view: &specular_image.texture_view,
231
sampler: &diffuse_image.sampler,
232
};
233
}
234
235
RenderViewEnvironmentMapBindGroupEntries::Single {
236
diffuse_texture_view: &fallback_image.cube.texture_view,
237
specular_texture_view: &fallback_image.cube.texture_view,
238
sampler: &fallback_image.cube.sampler,
239
}
240
}
241
}
242
243
impl LightProbeComponent for EnvironmentMapLight {
244
type AssetId = EnvironmentMapIds;
245
246
// Information needed to render with the environment map attached to the
247
// view.
248
type ViewLightProbeInfo = EnvironmentMapViewLightProbeInfo;
249
250
type QueryData = Option<Read<ParallaxCorrection>>;
251
252
fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId> {
253
if image_assets.get(&self.diffuse_map).is_none()
254
|| image_assets.get(&self.specular_map).is_none()
255
{
256
None
257
} else {
258
Some(EnvironmentMapIds {
259
diffuse: self.diffuse_map.id(),
260
specular: self.specular_map.id(),
261
})
262
}
263
}
264
265
fn intensity(&self) -> f32 {
266
self.intensity
267
}
268
269
fn flags(
270
&self,
271
maybe_parallax_correction: &<Self::QueryData as QueryData>::Item<'_, '_>,
272
) -> RenderLightProbeFlags {
273
let mut flags = RenderLightProbeFlags::empty();
274
if self.affects_lightmapped_mesh_diffuse {
275
flags.insert(RenderLightProbeFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE);
276
}
277
if maybe_parallax_correction.is_some_and(|parallax_correction| {
278
!matches!(*parallax_correction, ParallaxCorrection::None)
279
}) {
280
flags.insert(RenderLightProbeFlags::ENABLE_PARALLAX_CORRECTION);
281
}
282
flags
283
}
284
285
fn create_render_view_light_probes(
286
view_component: Option<&EnvironmentMapLight>,
287
image_assets: &RenderAssets<GpuImage>,
288
) -> RenderViewLightProbes<Self> {
289
let mut render_view_light_probes = RenderViewLightProbes::new();
290
291
// Find the index of the cubemap associated with the view, and determine
292
// its smallest mip level.
293
if let Some(EnvironmentMapLight {
294
diffuse_map: diffuse_map_handle,
295
specular_map: specular_map_handle,
296
intensity,
297
affects_lightmapped_mesh_diffuse,
298
..
299
}) = view_component
300
&& let (Some(_), Some(specular_map)) = (
301
image_assets.get(diffuse_map_handle),
302
image_assets.get(specular_map_handle),
303
)
304
{
305
render_view_light_probes.view_light_probe_info =
306
Some(EnvironmentMapViewLightProbeInfo {
307
cubemap_index: render_view_light_probes.get_or_insert_cubemap(
308
&EnvironmentMapIds {
309
diffuse: diffuse_map_handle.id(),
310
specular: specular_map_handle.id(),
311
},
312
) as i32,
313
smallest_specular_mip_level: specular_map.texture_descriptor.mip_level_count
314
- 1,
315
intensity: *intensity,
316
affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse,
317
});
318
};
319
320
render_view_light_probes
321
}
322
323
fn get_world_from_light_matrix(&self, original_transform: &Affine3A) -> Affine3A {
324
// Take the `rotation` field into account.
325
*original_transform * Affine3A::from_quat(self.rotation)
326
}
327
328
fn parallax_correction_bounds(
329
&self,
330
maybe_parallax_correction: &<Self::QueryData as QueryData>::Item<'_, '_>,
331
) -> Vec3 {
332
match *maybe_parallax_correction {
333
Some(&ParallaxCorrection::Custom(bounds)) => bounds,
334
Some(&ParallaxCorrection::Auto) => Vec3::splat(0.5),
335
Some(&ParallaxCorrection::None) | None => Vec3::ZERO,
336
}
337
}
338
}
339
340
impl Default for EnvironmentMapViewLightProbeInfo {
341
fn default() -> Self {
342
Self {
343
cubemap_index: -1,
344
smallest_specular_mip_level: 0,
345
intensity: 1.0,
346
affects_lightmapped_mesh_diffuse: true,
347
}
348
}
349
}
350
351