Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_light/src/probe.rs
9359 views
1
use bevy_asset::{Assets, Handle, RenderAssetUsages};
2
use bevy_camera::visibility::{self, ViewVisibility, Visibility, VisibilityClass};
3
use bevy_color::{Color, ColorToComponents, Srgba};
4
use bevy_ecs::prelude::*;
5
use bevy_image::Image;
6
use bevy_math::{Quat, UVec2, Vec3};
7
use bevy_reflect::prelude::*;
8
use bevy_transform::components::Transform;
9
use wgpu_types::{
10
Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension,
11
};
12
13
use crate::cluster::ClusterVisibilityClass;
14
15
/// A marker component for a light probe, which is a cuboid region that provides
16
/// global illumination to all fragments inside it.
17
///
18
/// Note that a light probe will have no effect unless the entity contains some
19
/// kind of illumination, which can either be an [`EnvironmentMapLight`] or an
20
/// [`IrradianceVolume`].
21
///
22
/// The light probe range is conceptually a unit cube (1×1×1) centered on the
23
/// origin. The [`Transform`] applied to this entity can scale, rotate, or
24
/// translate that cube so that it contains all fragments that should take this
25
/// light probe into account.
26
///
27
/// Light probes may specify a *falloff* range over which their influence tapers
28
/// off. The falloff range is expressed as a range from 0, representing
29
/// infinitely-sharp falloff, to 1, representing the most gradual falloff,
30
/// *inside* the 1×1×1 cube. So, for example, if you set the falloff to 0.5 on
31
/// an axis, then any fragments with positions between 0.0 units to 0.25 units
32
/// on that axis will receive 100% influence from the light probe, while
33
/// fragments with positions between 0.25 units to 0.5 units on that axis will
34
/// receive gradually-diminished influence, and fragments more than 0.5 units
35
/// from the center of the light probe will receive no influence at all.
36
///
37
/// When multiple sources of indirect illumination can be applied to a fragment,
38
/// the highest-quality ones are chosen. Diffuse and specular illumination are
39
/// considered separately, so, for example, Bevy may decide to sample the
40
/// diffuse illumination from an irradiance volume and the specular illumination
41
/// from a reflection probe. From highest priority to lowest priority, the
42
/// ranking is as follows:
43
///
44
/// | Rank | Diffuse | Specular |
45
/// | ---- | -------------------- | -------------------- |
46
/// | 1 | Lightmap | Lightmap |
47
/// | 2 | Irradiance volume | Reflection probe |
48
/// | 3 | Reflection probe | View environment map |
49
/// | 4 | View environment map | |
50
///
51
/// Note that ambient light is always added to the diffuse component and does
52
/// not participate in the ranking. That is, ambient light is applied in
53
/// addition to, not instead of, the light sources above.
54
///
55
/// Multiple light probes of the same type can apply to a single fragment. By
56
/// setting falloff regions appropriately, one can achieve a gradual blend from
57
/// one reflection probe and/or irradiance volume to another as objects move
58
/// between them.
59
///
60
/// A terminology note: Unfortunately, there is little agreement across game and
61
/// graphics engines as to what to call the various techniques that Bevy groups
62
/// under the term *light probe*. In Bevy, a *light probe* is the generic term
63
/// that encompasses both *reflection probes* and *irradiance volumes*. In
64
/// object-oriented terms, *light probe* is the superclass, and *reflection
65
/// probe* and *irradiance volume* are subclasses. In other engines, you may see
66
/// the term *light probe* refer to an irradiance volume with a single voxel, or
67
/// perhaps some other technique, while in Bevy *light probe* refers not to a
68
/// specific technique but rather to a class of techniques. Developers familiar
69
/// with other engines should be aware of this terminology difference.
70
#[derive(Component, Debug, Clone, Copy, Default, Reflect)]
71
#[reflect(Component, Default, Debug, Clone)]
72
#[require(Transform, ViewVisibility, Visibility, VisibilityClass)]
73
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
74
pub struct LightProbe {
75
/// The distance over which the effect of the light probe becomes weaker, on
76
/// each axis.
77
///
78
/// This is specified as a ratio of the total distance on each axis. So, for
79
/// example, if you specify `Vec3::splat(0.25)` here, then the light probe
80
/// will consist of a 0.75×0.75×0.75 unit cube within which fragments
81
/// receive the maximum influence from the light probe, contained within a
82
/// 1×1×1 cube which influences fragments inside it in a manner that
83
/// diminishes as fragments get farther from its center.
84
///
85
/// Falloff doesn't affect the influence range of the light probe itself;
86
/// it's still conceptually a 1×1×1 cube, regardless of the falloff setting.
87
/// In other words, falloff modifies the *interior* of the light probe cube
88
/// instead of increasing the *exterior* boundaries of the cube.
89
pub falloff: Vec3,
90
}
91
92
impl LightProbe {
93
/// Creates a new light probe component.
94
#[inline]
95
pub fn new() -> Self {
96
Self::default()
97
}
98
}
99
100
/// A pair of cubemap textures that represent the surroundings of a specific
101
/// area in space.
102
///
103
/// See `bevy_pbr::environment_map` for detailed information.
104
#[derive(Clone, Component, Reflect)]
105
#[reflect(Component, Default, Clone)]
106
pub struct EnvironmentMapLight {
107
/// The blurry image that represents diffuse radiance surrounding a region.
108
pub diffuse_map: Handle<Image>,
109
110
/// The typically-sharper, mipmapped image that represents specular radiance
111
/// surrounding a region.
112
pub specular_map: Handle<Image>,
113
114
/// Scale factor applied to the diffuse and specular light generated by this component.
115
///
116
/// After applying this multiplier, the resulting values should
117
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
118
///
119
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
120
pub intensity: f32,
121
122
/// World space rotation applied to the environment light cubemaps.
123
/// This is useful for users who require a different axis, such as the Z-axis, to serve
124
/// as the vertical axis.
125
pub rotation: Quat,
126
127
/// Whether the light from this environment map contributes diffuse lighting
128
/// to meshes with lightmaps.
129
///
130
/// Set this to false if your lightmap baking tool bakes the diffuse light
131
/// from this environment light into the lightmaps in order to avoid
132
/// counting the radiance from this environment map twice.
133
///
134
/// By default, this is set to true.
135
pub affects_lightmapped_mesh_diffuse: bool,
136
}
137
138
impl EnvironmentMapLight {
139
/// An environment map with a uniform color, useful for uniform ambient lighting.
140
pub fn solid_color(assets: &mut Assets<Image>, color: impl Into<Color>) -> Self {
141
let color = color.into();
142
Self::hemispherical_gradient(assets, color, color, color)
143
}
144
145
/// An environment map with a hemispherical gradient, fading between the sky and ground colors
146
/// at the horizon. Useful as a very simple 'sky'.
147
pub fn hemispherical_gradient(
148
assets: &mut Assets<Image>,
149
top_color: impl Into<Color>,
150
mid_color: impl Into<Color>,
151
bottom_color: impl Into<Color>,
152
) -> Self {
153
let handle = assets.add(Self::hemispherical_gradient_cubemap(
154
top_color.into(),
155
mid_color.into(),
156
bottom_color.into(),
157
));
158
159
Self {
160
diffuse_map: handle.clone(),
161
specular_map: handle,
162
..Default::default()
163
}
164
}
165
166
pub(crate) fn hemispherical_gradient_cubemap(
167
top_color: Color,
168
mid_color: Color,
169
bottom_color: Color,
170
) -> Image {
171
let top_color: Srgba = top_color.into();
172
let mid_color: Srgba = mid_color.into();
173
let bottom_color: Srgba = bottom_color.into();
174
Image {
175
texture_view_descriptor: Some(TextureViewDescriptor {
176
dimension: Some(TextureViewDimension::Cube),
177
..Default::default()
178
}),
179
..Image::new(
180
Extent3d {
181
width: 1,
182
height: 1,
183
depth_or_array_layers: 6,
184
},
185
TextureDimension::D2,
186
[
187
mid_color,
188
mid_color,
189
top_color,
190
bottom_color,
191
mid_color,
192
mid_color,
193
]
194
.into_iter()
195
.flat_map(|c| c.to_f32_array().map(half::f16::from_f32))
196
.flat_map(half::f16::to_le_bytes)
197
.collect(),
198
TextureFormat::Rgba16Float,
199
RenderAssetUsages::RENDER_WORLD,
200
)
201
}
202
}
203
}
204
205
impl Default for EnvironmentMapLight {
206
fn default() -> Self {
207
EnvironmentMapLight {
208
diffuse_map: Handle::default(),
209
specular_map: Handle::default(),
210
intensity: 0.0,
211
rotation: Quat::IDENTITY,
212
affects_lightmapped_mesh_diffuse: true,
213
}
214
}
215
}
216
217
/// Adds a skybox to a 3D camera, based on a cubemap texture.
218
///
219
/// Note that this component does not (currently) affect the scene's lighting.
220
/// To do so, use [`EnvironmentMapLight`] alongside this component.
221
///
222
/// See also <https://en.wikipedia.org/wiki/Skybox_(video_games)>.
223
#[derive(Component, Clone, Reflect)]
224
#[reflect(Component, Default, Clone)]
225
pub struct Skybox {
226
/// The cubemap to use.
227
pub image: Handle<Image>,
228
/// Scale factor applied to the skybox image.
229
/// After applying this multiplier to the image samples, the resulting values should
230
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
231
pub brightness: f32,
232
233
/// View space rotation applied to the skybox cubemap.
234
/// This is useful for users who require a different axis, such as the Z-axis, to serve
235
/// as the vertical axis.
236
pub rotation: Quat,
237
}
238
239
impl Default for Skybox {
240
fn default() -> Self {
241
Skybox {
242
image: Handle::default(),
243
brightness: 0.0,
244
rotation: Quat::IDENTITY,
245
}
246
}
247
}
248
249
/// A generated environment map that is filtered at runtime.
250
///
251
/// See `bevy_pbr::light_probe::generate` for detailed information.
252
#[derive(Clone, Component, Reflect)]
253
#[reflect(Component, Default, Clone)]
254
pub struct GeneratedEnvironmentMapLight {
255
/// Source cubemap to be filtered on the GPU, size must be a power of two.
256
pub environment_map: Handle<Image>,
257
258
/// Scale factor applied to the diffuse and specular light generated by this
259
/// component. Expressed in cd/m² (candela per square meter).
260
pub intensity: f32,
261
262
/// World-space rotation applied to the cubemap.
263
pub rotation: Quat,
264
265
/// Whether this light contributes diffuse lighting to meshes that already
266
/// have baked lightmaps.
267
pub affects_lightmapped_mesh_diffuse: bool,
268
}
269
270
impl Default for GeneratedEnvironmentMapLight {
271
fn default() -> Self {
272
GeneratedEnvironmentMapLight {
273
environment_map: Handle::default(),
274
intensity: 0.0,
275
rotation: Quat::IDENTITY,
276
affects_lightmapped_mesh_diffuse: true,
277
}
278
}
279
}
280
281
/// Lets the atmosphere contribute environment lighting (reflections and ambient diffuse) to your scene.
282
///
283
/// Attach this to a [`Camera3d`](bevy_camera::Camera3d) to light the entire view, or to a
284
/// [`LightProbe`] to light only a specific region.
285
/// Behind the scenes, this generates an environment map from the atmosphere for image-based lighting
286
/// and inserts a corresponding [`GeneratedEnvironmentMapLight`].
287
///
288
/// For HDRI-based lighting, use a preauthored [`EnvironmentMapLight`] or filter one at runtime with
289
/// [`GeneratedEnvironmentMapLight`].
290
#[derive(Component, Clone)]
291
pub struct AtmosphereEnvironmentMapLight {
292
/// Controls how bright the atmosphere's environment lighting is.
293
/// Increase this value to brighten reflections and ambient diffuse lighting.
294
///
295
/// The default is `1.0` so that the generated environment lighting matches
296
/// the light intensity of the atmosphere in the scene.
297
pub intensity: f32,
298
/// Whether the diffuse contribution should affect meshes that already have lightmaps.
299
pub affects_lightmapped_mesh_diffuse: bool,
300
/// Cubemap resolution in pixels (must be a power-of-two).
301
pub size: UVec2,
302
}
303
304
impl Default for AtmosphereEnvironmentMapLight {
305
fn default() -> Self {
306
Self {
307
intensity: 1.0,
308
affects_lightmapped_mesh_diffuse: true,
309
size: UVec2::new(512, 512),
310
}
311
}
312
}
313
314
/// The component that defines an irradiance volume.
315
///
316
/// See `bevy_pbr::irradiance_volume` for detailed information.
317
///
318
/// This component requires the [`LightProbe`] component, and is typically used with
319
/// [`bevy_transform::components::Transform`] to place the volume appropriately.
320
#[derive(Clone, Reflect, Component, Debug)]
321
#[reflect(Component, Default, Debug, Clone)]
322
#[require(LightProbe)]
323
pub struct IrradianceVolume {
324
/// The 3D texture that represents the ambient cubes, encoded in the format
325
/// described in `bevy_pbr::irradiance_volume`.
326
pub voxels: Handle<Image>,
327
328
/// Scale factor applied to the diffuse and specular light generated by this component.
329
///
330
/// After applying this multiplier, the resulting values should
331
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
332
///
333
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
334
pub intensity: f32,
335
336
/// Whether the light from this irradiance volume has an effect on meshes
337
/// with lightmaps.
338
///
339
/// Set this to false if your lightmap baking tool bakes the light from this
340
/// irradiance volume into the lightmaps in order to avoid counting the
341
/// irradiance twice. Frequently, applications use irradiance volumes as a
342
/// lower-quality alternative to lightmaps for capturing indirect
343
/// illumination on dynamic objects, and such applications will want to set
344
/// this value to false.
345
///
346
/// By default, this is set to true.
347
pub affects_lightmapped_meshes: bool,
348
}
349
350
impl Default for IrradianceVolume {
351
#[inline]
352
fn default() -> Self {
353
IrradianceVolume {
354
voxels: Handle::default(),
355
intensity: 0.0,
356
affects_lightmapped_meshes: true,
357
}
358
}
359
}
360
361
/// Add this component to a reflection probe to customize *parallax correction*.
362
///
363
/// For environment maps added directly to a camera, Bevy renders the reflected
364
/// scene that a cubemap captures as though it were infinitely far away. This is
365
/// acceptable if the cubemap captures very distant objects, such as distant
366
/// mountains in outdoor scenes. It's less ideal, however, if the cubemap
367
/// reflects near objects, such as the interior of a room. Therefore, by default
368
/// for reflection probes Bevy uses *parallax-corrected cubemaps* (PCCM), which
369
/// causes Bevy to treat the reflected scene as though it coincided with the
370
/// boundaries of the light probe.
371
///
372
/// As an example, for indoor scenes, it's common to place reflection probes
373
/// inside each room and to make the boundaries of the reflection probe (as
374
/// determined by the light probe's [`bevy_transform::components::Transform`])
375
/// coincide with the walls of the room. That way, the reflection probes will
376
/// (1) apply to the objects inside the room and (2) take the positions of those
377
/// objects into account in order to create a realistic reflection.
378
///
379
/// Instead of having the simulated boundaries of the reflected area coincide
380
/// with the boundaries of the light probe, it's also possible to specify
381
/// *custom* parallax correction boundaries, so that the region of influence of
382
/// the light probe doesn't correspond with the simulated boundaries used for
383
/// parallax correction. This is commonly used when the boundaries of the light
384
/// probe are slightly larger than the room that the light probe contains, for
385
/// instance in order to avoid artifacts along the edges of the room that occur
386
/// due to rounding error, or else when the *falloff* feature is used that
387
/// blends reflection probes into adjacent ones.
388
///
389
/// Place this component on an entity that has a [`LightProbe`] and
390
/// [`EnvironmentMapLight`] component in order to either (1) opt out of parallax
391
/// correction via [`ParallaxCorrection::None`] or (2) specify custom parallax
392
/// correction boundaries via [`ParallaxCorrection::Custom`]. If you don't
393
/// manually place this component on a reflection probe, Bevy will automatically
394
/// add a [`ParallaxCorrection::Auto`] component so that the boundaries of the
395
/// light probe will coincide with the simulated boundaries used for parallax
396
/// correction.
397
///
398
/// See the `pccm` example for an example of usage of parallax-corrected
399
/// cubemaps and the `light_probe_blending` example for an example of use of
400
/// custom parallax correction boundaries.
401
#[derive(Clone, Copy, Default, Component, Reflect)]
402
#[reflect(Clone, Default, Component)]
403
pub enum ParallaxCorrection {
404
/// No parallax correction is used.
405
///
406
/// This component causes Bevy to render the reflection as though the
407
/// reflected surface were infinitely distant.
408
None,
409
410
/// The parallax correction boundaries correspond with the boundaries of the
411
/// light probe.
412
///
413
/// This is the default value. Bevy automatically adds this component value
414
/// to reflection probes that don't have a [`ParallaxCorrection`] component.
415
/// It's equivalent to `ParallaxCorrection::Custom(Vec3::splat(0.5))`.
416
#[default]
417
Auto,
418
419
/// The parallax correction boundaries are specified manually.
420
///
421
/// The simulated reflection boundaries are specified as an axis-aligned
422
/// cube *in light probe space* with the given *half* extents. Thus, for
423
/// example, if you set the parallax correction boundaries to `vec3(0.5,
424
/// 1.0, 2.0)` and the scale of the light probe is `vec3(3.0, 3.0, 3.0)`,
425
/// then the simulated boundaries of the reflected area used for parallax
426
/// correction will be centered on the reflection probe with a width of 3.0
427
/// m, a height of 6.0 m, and a depth of 12.0 m.
428
Custom(Vec3),
429
}
430
431
/// A system that automatically adds a [`ParallaxCorrection::Auto`] component to
432
/// any reflection probe that doesn't already have a [`ParallaxCorrection`]
433
/// component.
434
///
435
/// A reflection probe is any entity with both an [`EnvironmentMapLight`] and a
436
/// [`LightProbe`] component.
437
pub fn automatically_add_parallax_correction_components(
438
mut commands: Commands,
439
query: Query<
440
Entity,
441
(
442
With<EnvironmentMapLight>,
443
With<LightProbe>,
444
Without<ParallaxCorrection>,
445
),
446
>,
447
) {
448
for entity in &query {
449
commands
450
.entity(entity)
451
.insert(ParallaxCorrection::default());
452
}
453
}
454
455