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
6604 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::{query::QueryItem, system::lifetimeless::Read};
49
use bevy_image::Image;
50
use bevy_light::EnvironmentMapLight;
51
use bevy_render::{
52
extract_instances::ExtractInstance,
53
render_asset::RenderAssets,
54
render_resource::{
55
binding_types::{self, uniform_buffer},
56
BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, ShaderStages, TextureSampleType,
57
TextureView,
58
},
59
renderer::{RenderAdapter, RenderDevice},
60
texture::{FallbackImage, GpuImage},
61
};
62
63
use core::{num::NonZero, ops::Deref};
64
65
use crate::{
66
add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform,
67
MAX_VIEW_LIGHT_PROBES,
68
};
69
70
use super::{LightProbeComponent, RenderViewLightProbes};
71
72
/// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles.
73
///
74
/// This is for use in the render app.
75
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
76
pub struct EnvironmentMapIds {
77
/// The blurry image that represents diffuse radiance surrounding a region.
78
pub(crate) diffuse: AssetId<Image>,
79
/// The typically-sharper, mipmapped image that represents specular radiance
80
/// surrounding a region.
81
pub(crate) specular: AssetId<Image>,
82
}
83
84
/// All the bind group entries necessary for PBR shaders to access the
85
/// environment maps exposed to a view.
86
pub(crate) enum RenderViewEnvironmentMapBindGroupEntries<'a> {
87
/// The version used when binding arrays aren't available on the current
88
/// platform.
89
Single {
90
/// The texture view of the view's diffuse cubemap.
91
diffuse_texture_view: &'a TextureView,
92
93
/// The texture view of the view's specular cubemap.
94
specular_texture_view: &'a TextureView,
95
96
/// The sampler used to sample elements of both `diffuse_texture_views` and
97
/// `specular_texture_views`.
98
sampler: &'a Sampler,
99
},
100
101
/// The version used when binding arrays are available on the current
102
/// platform.
103
Multiple {
104
/// A texture view of each diffuse cubemap, in the same order that they are
105
/// supplied to the view (i.e. in the same order as
106
/// `binding_index_to_cubemap` in [`RenderViewLightProbes`]).
107
///
108
/// This is a vector of `wgpu::TextureView`s. But we don't want to import
109
/// `wgpu` in this crate, so we refer to it indirectly like this.
110
diffuse_texture_views: Vec<&'a <TextureView as Deref>::Target>,
111
112
/// As above, but for specular cubemaps.
113
specular_texture_views: Vec<&'a <TextureView as Deref>::Target>,
114
115
/// The sampler used to sample elements of both `diffuse_texture_views` and
116
/// `specular_texture_views`.
117
sampler: &'a Sampler,
118
},
119
}
120
121
/// Information about the environment map attached to the view, if any. This is
122
/// a global environment map that lights everything visible in the view, as
123
/// opposed to a light probe which affects only a specific area.
124
pub struct EnvironmentMapViewLightProbeInfo {
125
/// The index of the diffuse and specular cubemaps in the binding arrays.
126
pub(crate) cubemap_index: i32,
127
/// The smallest mip level of the specular cubemap.
128
pub(crate) smallest_specular_mip_level: u32,
129
/// The scale factor applied to the diffuse and specular light in the
130
/// cubemap. This is in units of cd/m² (candela per square meter).
131
pub(crate) intensity: f32,
132
/// Whether this lightmap affects the diffuse lighting of lightmapped
133
/// meshes.
134
pub(crate) affects_lightmapped_mesh_diffuse: bool,
135
}
136
137
impl ExtractInstance for EnvironmentMapIds {
138
type QueryData = Read<EnvironmentMapLight>;
139
140
type QueryFilter = ();
141
142
fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self> {
143
Some(EnvironmentMapIds {
144
diffuse: item.diffuse_map.id(),
145
specular: item.specular_map.id(),
146
})
147
}
148
}
149
150
/// Returns the bind group layout entries for the environment map diffuse and
151
/// specular binding arrays respectively, in addition to the sampler.
152
pub(crate) fn get_bind_group_layout_entries(
153
render_device: &RenderDevice,
154
render_adapter: &RenderAdapter,
155
) -> [BindGroupLayoutEntryBuilder; 4] {
156
let mut texture_cube_binding =
157
binding_types::texture_cube(TextureSampleType::Float { filterable: true });
158
if binding_arrays_are_usable(render_device, render_adapter) {
159
texture_cube_binding =
160
texture_cube_binding.count(NonZero::<u32>::new(MAX_VIEW_LIGHT_PROBES as _).unwrap());
161
}
162
163
[
164
texture_cube_binding,
165
texture_cube_binding,
166
binding_types::sampler(SamplerBindingType::Filtering),
167
uniform_buffer::<EnvironmentMapUniform>(true).visibility(ShaderStages::FRAGMENT),
168
]
169
}
170
171
impl<'a> RenderViewEnvironmentMapBindGroupEntries<'a> {
172
/// Looks up and returns the bindings for the environment map diffuse and
173
/// specular binding arrays respectively, as well as the sampler.
174
pub(crate) fn get(
175
render_view_environment_maps: Option<&RenderViewLightProbes<EnvironmentMapLight>>,
176
images: &'a RenderAssets<GpuImage>,
177
fallback_image: &'a FallbackImage,
178
render_device: &RenderDevice,
179
render_adapter: &RenderAdapter,
180
) -> RenderViewEnvironmentMapBindGroupEntries<'a> {
181
if binding_arrays_are_usable(render_device, render_adapter) {
182
let mut diffuse_texture_views = vec![];
183
let mut specular_texture_views = vec![];
184
let mut sampler = None;
185
186
if let Some(environment_maps) = render_view_environment_maps {
187
for &cubemap_id in &environment_maps.binding_index_to_textures {
188
add_cubemap_texture_view(
189
&mut diffuse_texture_views,
190
&mut sampler,
191
cubemap_id.diffuse,
192
images,
193
fallback_image,
194
);
195
add_cubemap_texture_view(
196
&mut specular_texture_views,
197
&mut sampler,
198
cubemap_id.specular,
199
images,
200
fallback_image,
201
);
202
}
203
}
204
205
// Pad out the bindings to the size of the binding array using fallback
206
// textures. This is necessary on D3D12 and Metal.
207
diffuse_texture_views.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);
208
specular_texture_views
209
.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);
210
211
return RenderViewEnvironmentMapBindGroupEntries::Multiple {
212
diffuse_texture_views,
213
specular_texture_views,
214
sampler: sampler.unwrap_or(&fallback_image.cube.sampler),
215
};
216
}
217
218
if let Some(environment_maps) = render_view_environment_maps
219
&& let Some(cubemap) = environment_maps.binding_index_to_textures.first()
220
&& let (Some(diffuse_image), Some(specular_image)) =
221
(images.get(cubemap.diffuse), images.get(cubemap.specular))
222
{
223
return RenderViewEnvironmentMapBindGroupEntries::Single {
224
diffuse_texture_view: &diffuse_image.texture_view,
225
specular_texture_view: &specular_image.texture_view,
226
sampler: &diffuse_image.sampler,
227
};
228
}
229
230
RenderViewEnvironmentMapBindGroupEntries::Single {
231
diffuse_texture_view: &fallback_image.cube.texture_view,
232
specular_texture_view: &fallback_image.cube.texture_view,
233
sampler: &fallback_image.cube.sampler,
234
}
235
}
236
}
237
238
impl LightProbeComponent for EnvironmentMapLight {
239
type AssetId = EnvironmentMapIds;
240
241
// Information needed to render with the environment map attached to the
242
// view.
243
type ViewLightProbeInfo = EnvironmentMapViewLightProbeInfo;
244
245
fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId> {
246
if image_assets.get(&self.diffuse_map).is_none()
247
|| image_assets.get(&self.specular_map).is_none()
248
{
249
None
250
} else {
251
Some(EnvironmentMapIds {
252
diffuse: self.diffuse_map.id(),
253
specular: self.specular_map.id(),
254
})
255
}
256
}
257
258
fn intensity(&self) -> f32 {
259
self.intensity
260
}
261
262
fn affects_lightmapped_mesh_diffuse(&self) -> bool {
263
self.affects_lightmapped_mesh_diffuse
264
}
265
266
fn create_render_view_light_probes(
267
view_component: Option<&EnvironmentMapLight>,
268
image_assets: &RenderAssets<GpuImage>,
269
) -> RenderViewLightProbes<Self> {
270
let mut render_view_light_probes = RenderViewLightProbes::new();
271
272
// Find the index of the cubemap associated with the view, and determine
273
// its smallest mip level.
274
if let Some(EnvironmentMapLight {
275
diffuse_map: diffuse_map_handle,
276
specular_map: specular_map_handle,
277
intensity,
278
affects_lightmapped_mesh_diffuse,
279
..
280
}) = view_component
281
&& let (Some(_), Some(specular_map)) = (
282
image_assets.get(diffuse_map_handle),
283
image_assets.get(specular_map_handle),
284
)
285
{
286
render_view_light_probes.view_light_probe_info = EnvironmentMapViewLightProbeInfo {
287
cubemap_index: render_view_light_probes.get_or_insert_cubemap(&EnvironmentMapIds {
288
diffuse: diffuse_map_handle.id(),
289
specular: specular_map_handle.id(),
290
}) as i32,
291
smallest_specular_mip_level: specular_map.mip_level_count - 1,
292
intensity: *intensity,
293
affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse,
294
};
295
};
296
297
render_view_light_probes
298
}
299
}
300
301
impl Default for EnvironmentMapViewLightProbeInfo {
302
fn default() -> Self {
303
Self {
304
cubemap_index: -1,
305
smallest_specular_mip_level: 0,
306
intensity: 1.0,
307
affects_lightmapped_mesh_diffuse: true,
308
}
309
}
310
}
311
312