Skip to main content

bevy_light/
probe.rs

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