use bevy_asset::{Assets, Handle, RenderAssetUsages};1use bevy_camera::visibility::{self, ViewVisibility, Visibility, VisibilityClass};2use bevy_color::{Color, ColorToComponents, Srgba};3use bevy_ecs::prelude::*;4use bevy_image::Image;5use bevy_math::{Quat, UVec2, Vec3};6use bevy_reflect::prelude::*;7use bevy_transform::components::Transform;8use wgpu_types::{9Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor, TextureViewDimension,10};1112use crate::cluster::ClusterVisibilityClass;1314/// A marker component for a light probe, which is a cuboid region that provides15/// global illumination to all fragments inside it.16///17/// Note that a light probe will have no effect unless the entity contains some18/// kind of illumination, which can either be an [`EnvironmentMapLight`] or an19/// [`IrradianceVolume`].20///21/// The light probe range is conceptually a unit cube (1×1×1) centered on the22/// origin. The [`Transform`] applied to this entity can scale, rotate, or23/// translate that cube so that it contains all fragments that should take this24/// light probe into account.25///26/// Light probes may specify a *falloff* range over which their influence tapers27/// off. The falloff range is expressed as a range from 0, representing28/// infinitely-sharp falloff, to 1, representing the most gradual falloff,29/// *inside* the 1×1×1 cube. So, for example, if you set the falloff to 0.5 on30/// an axis, then any fragments with positions between 0.0 units to 0.25 units31/// on that axis will receive 100% influence from the light probe, while32/// fragments with positions between 0.25 units to 0.5 units on that axis will33/// receive gradually-diminished influence, and fragments more than 0.5 units34/// from the center of the light probe will receive no influence at all.35///36/// When multiple sources of indirect illumination can be applied to a fragment,37/// the highest-quality ones are chosen. Diffuse and specular illumination are38/// considered separately, so, for example, Bevy may decide to sample the39/// diffuse illumination from an irradiance volume and the specular illumination40/// from a reflection probe. From highest priority to lowest priority, the41/// ranking is as follows:42///43/// | Rank | Diffuse | Specular |44/// | ---- | -------------------- | -------------------- |45/// | 1 | Lightmap | Lightmap |46/// | 2 | Irradiance volume | Reflection probe |47/// | 3 | Reflection probe | View environment map |48/// | 4 | View environment map | |49///50/// Note that ambient light is always added to the diffuse component and does51/// not participate in the ranking. That is, ambient light is applied in52/// addition to, not instead of, the light sources above.53///54/// Multiple light probes of the same type can apply to a single fragment. By55/// setting falloff regions appropriately, one can achieve a gradual blend from56/// one reflection probe and/or irradiance volume to another as objects move57/// between them.58///59/// A terminology note: Unfortunately, there is little agreement across game and60/// graphics engines as to what to call the various techniques that Bevy groups61/// under the term *light probe*. In Bevy, a *light probe* is the generic term62/// that encompasses both *reflection probes* and *irradiance volumes*. In63/// object-oriented terms, *light probe* is the superclass, and *reflection64/// probe* and *irradiance volume* are subclasses. In other engines, you may see65/// the term *light probe* refer to an irradiance volume with a single voxel, or66/// perhaps some other technique, while in Bevy *light probe* refers not to a67/// specific technique but rather to a class of techniques. Developers familiar68/// with other engines should be aware of this terminology difference.69#[derive(Component, Debug, Clone, Copy, Default, Reflect)]70#[reflect(Component, Default, Debug, Clone)]71#[require(Transform, ViewVisibility, Visibility, VisibilityClass)]72#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]73pub struct LightProbe {74/// The distance over which the effect of the light probe becomes weaker, on75/// each axis.76///77/// This is specified as a ratio of the total distance on each axis. So, for78/// example, if you specify `Vec3::splat(0.25)` here, then the light probe79/// will consist of a 0.75×0.75×0.75 unit cube within which fragments80/// receive the maximum influence from the light probe, contained within a81/// 1×1×1 cube which influences fragments inside it in a manner that82/// diminishes as fragments get farther from its center.83///84/// Falloff doesn't affect the influence range of the light probe itself;85/// it's still conceptually a 1×1×1 cube, regardless of the falloff setting.86/// In other words, falloff modifies the *interior* of the light probe cube87/// instead of increasing the *exterior* boundaries of the cube.88pub falloff: Vec3,89}9091impl LightProbe {92/// Creates a new light probe component.93#[inline]94pub fn new() -> Self {95Self::default()96}97}9899/// A pair of cubemap textures that represent the surroundings of a specific100/// area in space.101///102/// See `bevy_pbr::environment_map` for detailed information.103#[derive(Clone, Component, Reflect)]104#[reflect(Component, Default, Clone)]105pub struct EnvironmentMapLight {106/// The blurry image that represents diffuse radiance surrounding a region.107pub diffuse_map: Handle<Image>,108109/// The typically-sharper, mipmapped image that represents specular radiance110/// surrounding a region.111pub specular_map: Handle<Image>,112113/// Scale factor applied to the diffuse and specular light generated by this component.114///115/// After applying this multiplier, the resulting values should116/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).117///118/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.119pub intensity: f32,120121/// World space rotation applied to the environment light cubemaps.122/// This is useful for users who require a different axis, such as the Z-axis, to serve123/// as the vertical axis.124pub rotation: Quat,125126/// Whether the light from this environment map contributes diffuse lighting127/// to meshes with lightmaps.128///129/// Set this to false if your lightmap baking tool bakes the diffuse light130/// from this environment light into the lightmaps in order to avoid131/// counting the radiance from this environment map twice.132///133/// By default, this is set to true.134pub affects_lightmapped_mesh_diffuse: bool,135}136137impl EnvironmentMapLight {138/// An environment map with a uniform color, useful for uniform ambient lighting.139pub fn solid_color(assets: &mut Assets<Image>, color: impl Into<Color>) -> Self {140let color = color.into();141Self::hemispherical_gradient(assets, color, color, color)142}143144/// An environment map with a hemispherical gradient, fading between the sky and ground colors145/// at the horizon. Useful as a very simple 'sky'.146pub fn hemispherical_gradient(147assets: &mut Assets<Image>,148top_color: impl Into<Color>,149mid_color: impl Into<Color>,150bottom_color: impl Into<Color>,151) -> Self {152let handle = assets.add(Self::hemispherical_gradient_cubemap(153top_color.into(),154mid_color.into(),155bottom_color.into(),156));157158Self {159diffuse_map: handle.clone(),160specular_map: handle,161..Default::default()162}163}164165pub(crate) fn hemispherical_gradient_cubemap(166top_color: Color,167mid_color: Color,168bottom_color: Color,169) -> Image {170let top_color: Srgba = top_color.into();171let mid_color: Srgba = mid_color.into();172let bottom_color: Srgba = bottom_color.into();173Image {174texture_view_descriptor: Some(TextureViewDescriptor {175dimension: Some(TextureViewDimension::Cube),176..Default::default()177}),178..Image::new(179Extent3d {180width: 1,181height: 1,182depth_or_array_layers: 6,183},184TextureDimension::D2,185[186mid_color,187mid_color,188top_color,189bottom_color,190mid_color,191mid_color,192]193.into_iter()194.flat_map(|c| c.to_f32_array().map(half::f16::from_f32))195.flat_map(half::f16::to_le_bytes)196.collect(),197TextureFormat::Rgba16Float,198RenderAssetUsages::RENDER_WORLD,199)200}201}202}203204impl Default for EnvironmentMapLight {205fn default() -> Self {206EnvironmentMapLight {207diffuse_map: Handle::default(),208specular_map: Handle::default(),209intensity: 0.0,210rotation: Quat::IDENTITY,211affects_lightmapped_mesh_diffuse: true,212}213}214}215216/// Adds a skybox to a 3D camera, based on a cubemap texture.217///218/// Note that this component does not (currently) affect the scene's lighting.219/// To do so, use [`EnvironmentMapLight`] alongside this component.220///221/// See also <https://en.wikipedia.org/wiki/Skybox_(video_games)>.222#[derive(Component, Clone, Reflect)]223#[reflect(Component, Default, Clone)]224pub struct Skybox {225/// The cubemap to use.226pub image: Handle<Image>,227/// Scale factor applied to the skybox image.228/// After applying this multiplier to the image samples, the resulting values should229/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).230pub brightness: f32,231232/// View space rotation applied to the skybox cubemap.233/// This is useful for users who require a different axis, such as the Z-axis, to serve234/// as the vertical axis.235pub rotation: Quat,236}237238impl Default for Skybox {239fn default() -> Self {240Skybox {241image: Handle::default(),242brightness: 0.0,243rotation: Quat::IDENTITY,244}245}246}247248/// A generated environment map that is filtered at runtime.249///250/// See `bevy_pbr::light_probe::generate` for detailed information.251#[derive(Clone, Component, Reflect)]252#[reflect(Component, Default, Clone)]253pub struct GeneratedEnvironmentMapLight {254/// Source cubemap to be filtered on the GPU, size must be a power of two.255pub environment_map: Handle<Image>,256257/// Scale factor applied to the diffuse and specular light generated by this258/// component. Expressed in cd/m² (candela per square meter).259pub intensity: f32,260261/// World-space rotation applied to the cubemap.262pub rotation: Quat,263264/// Whether this light contributes diffuse lighting to meshes that already265/// have baked lightmaps.266pub affects_lightmapped_mesh_diffuse: bool,267}268269impl Default for GeneratedEnvironmentMapLight {270fn default() -> Self {271GeneratedEnvironmentMapLight {272environment_map: Handle::default(),273intensity: 0.0,274rotation: Quat::IDENTITY,275affects_lightmapped_mesh_diffuse: true,276}277}278}279280/// Lets the atmosphere contribute environment lighting (reflections and ambient diffuse) to your scene.281///282/// Attach this to a [`Camera3d`](bevy_camera::Camera3d) to light the entire view, or to a283/// [`LightProbe`] to light only a specific region.284/// Behind the scenes, this generates an environment map from the atmosphere for image-based lighting285/// and inserts a corresponding [`GeneratedEnvironmentMapLight`].286///287/// For HDRI-based lighting, use a preauthored [`EnvironmentMapLight`] or filter one at runtime with288/// [`GeneratedEnvironmentMapLight`].289#[derive(Component, Clone)]290pub struct AtmosphereEnvironmentMapLight {291/// Controls how bright the atmosphere's environment lighting is.292/// Increase this value to brighten reflections and ambient diffuse lighting.293///294/// The default is `1.0` so that the generated environment lighting matches295/// the light intensity of the atmosphere in the scene.296pub intensity: f32,297/// Whether the diffuse contribution should affect meshes that already have lightmaps.298pub affects_lightmapped_mesh_diffuse: bool,299/// Cubemap resolution in pixels (must be a power-of-two).300pub size: UVec2,301}302303impl Default for AtmosphereEnvironmentMapLight {304fn default() -> Self {305Self {306intensity: 1.0,307affects_lightmapped_mesh_diffuse: true,308size: UVec2::new(512, 512),309}310}311}312313/// The component that defines an irradiance volume.314///315/// See `bevy_pbr::irradiance_volume` for detailed information.316///317/// This component requires the [`LightProbe`] component, and is typically used with318/// [`bevy_transform::components::Transform`] to place the volume appropriately.319#[derive(Clone, Reflect, Component, Debug)]320#[reflect(Component, Default, Debug, Clone)]321#[require(LightProbe)]322pub struct IrradianceVolume {323/// The 3D texture that represents the ambient cubes, encoded in the format324/// described in `bevy_pbr::irradiance_volume`.325pub voxels: Handle<Image>,326327/// Scale factor applied to the diffuse and specular light generated by this component.328///329/// After applying this multiplier, the resulting values should330/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).331///332/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.333pub intensity: f32,334335/// Whether the light from this irradiance volume has an effect on meshes336/// with lightmaps.337///338/// Set this to false if your lightmap baking tool bakes the light from this339/// irradiance volume into the lightmaps in order to avoid counting the340/// irradiance twice. Frequently, applications use irradiance volumes as a341/// lower-quality alternative to lightmaps for capturing indirect342/// illumination on dynamic objects, and such applications will want to set343/// this value to false.344///345/// By default, this is set to true.346pub affects_lightmapped_meshes: bool,347}348349impl Default for IrradianceVolume {350#[inline]351fn default() -> Self {352IrradianceVolume {353voxels: Handle::default(),354intensity: 0.0,355affects_lightmapped_meshes: true,356}357}358}359360/// Add this component to a reflection probe to customize *parallax correction*.361///362/// For environment maps added directly to a camera, Bevy renders the reflected363/// scene that a cubemap captures as though it were infinitely far away. This is364/// acceptable if the cubemap captures very distant objects, such as distant365/// mountains in outdoor scenes. It's less ideal, however, if the cubemap366/// reflects near objects, such as the interior of a room. Therefore, by default367/// for reflection probes Bevy uses *parallax-corrected cubemaps* (PCCM), which368/// causes Bevy to treat the reflected scene as though it coincided with the369/// boundaries of the light probe.370///371/// As an example, for indoor scenes, it's common to place reflection probes372/// inside each room and to make the boundaries of the reflection probe (as373/// determined by the light probe's [`bevy_transform::components::Transform`])374/// coincide with the walls of the room. That way, the reflection probes will375/// (1) apply to the objects inside the room and (2) take the positions of those376/// objects into account in order to create a realistic reflection.377///378/// Instead of having the simulated boundaries of the reflected area coincide379/// with the boundaries of the light probe, it's also possible to specify380/// *custom* parallax correction boundaries, so that the region of influence of381/// the light probe doesn't correspond with the simulated boundaries used for382/// parallax correction. This is commonly used when the boundaries of the light383/// probe are slightly larger than the room that the light probe contains, for384/// instance in order to avoid artifacts along the edges of the room that occur385/// due to rounding error, or else when the *falloff* feature is used that386/// blends reflection probes into adjacent ones.387///388/// Place this component on an entity that has a [`LightProbe`] and389/// [`EnvironmentMapLight`] component in order to either (1) opt out of parallax390/// correction via [`ParallaxCorrection::None`] or (2) specify custom parallax391/// correction boundaries via [`ParallaxCorrection::Custom`]. If you don't392/// manually place this component on a reflection probe, Bevy will automatically393/// add a [`ParallaxCorrection::Auto`] component so that the boundaries of the394/// light probe will coincide with the simulated boundaries used for parallax395/// correction.396///397/// See the `pccm` example for an example of usage of parallax-corrected398/// cubemaps and the `light_probe_blending` example for an example of use of399/// custom parallax correction boundaries.400#[derive(Clone, Copy, Default, Component, Reflect)]401#[reflect(Clone, Default, Component)]402pub enum ParallaxCorrection {403/// No parallax correction is used.404///405/// This component causes Bevy to render the reflection as though the406/// reflected surface were infinitely distant.407None,408409/// The parallax correction boundaries correspond with the boundaries of the410/// light probe.411///412/// This is the default value. Bevy automatically adds this component value413/// to reflection probes that don't have a [`ParallaxCorrection`] component.414/// It's equivalent to `ParallaxCorrection::Custom(Vec3::splat(0.5))`.415#[default]416Auto,417418/// The parallax correction boundaries are specified manually.419///420/// The simulated reflection boundaries are specified as an axis-aligned421/// cube *in light probe space* with the given *half* extents. Thus, for422/// example, if you set the parallax correction boundaries to `vec3(0.5,423/// 1.0, 2.0)` and the scale of the light probe is `vec3(3.0, 3.0, 3.0)`,424/// then the simulated boundaries of the reflected area used for parallax425/// correction will be centered on the reflection probe with a width of 3.0426/// m, a height of 6.0 m, and a depth of 12.0 m.427Custom(Vec3),428}429430/// A system that automatically adds a [`ParallaxCorrection::Auto`] component to431/// any reflection probe that doesn't already have a [`ParallaxCorrection`]432/// component.433///434/// A reflection probe is any entity with both an [`EnvironmentMapLight`] and a435/// [`LightProbe`] component.436pub fn automatically_add_parallax_correction_components(437mut commands: Commands,438query: Query<439Entity,440(441With<EnvironmentMapLight>,442With<LightProbe>,443Without<ParallaxCorrection>,444),445>,446) {447for entity in &query {448commands449.entity(entity)450.insert(ParallaxCorrection::default());451}452}453454455