Path: blob/main/crates/bevy_pbr/src/light_probe/environment_map.rs
9412 views
//! Environment maps and reflection probes.1//!2//! An *environment map* consists of a pair of diffuse and specular cubemaps3//! that together reflect the static surrounding area of a region in space. When4//! available, the PBR shader uses these to apply diffuse light and calculate5//! specular reflections.6//!7//! Environment maps come in two flavors, depending on what other components the8//! entities they're attached to have:9//!10//! 1. If attached to a view, they represent the objects located a very far11//! distance from the view, in a similar manner to a skybox. Essentially, these12//! *view environment maps* represent a higher-quality replacement for13//! [`AmbientLight`](bevy_light::AmbientLight) for outdoor scenes. The indirect light from such14//! environment maps are added to every point of the scene, including15//! interior enclosed areas.16//!17//! 2. If attached to a [`bevy_light::LightProbe`], environment maps represent the immediate18//! surroundings of a specific location in the scene. These types of19//! environment maps are known as *reflection probes*.20//!21//! Typically, environment maps are static (i.e. "baked", calculated ahead of22//! time) and so only reflect fixed static geometry. The environment maps must23//! be pre-filtered into a pair of cubemaps, one for the diffuse component and24//! one for the specular component, according to the [split-sum approximation].25//! To pre-filter your environment map, you can use the [glTF IBL Sampler] or26//! its [artist-friendly UI]. The diffuse map uses the Lambertian distribution,27//! while the specular map uses the GGX distribution.28//!29//! The Khronos Group has [several pre-filtered environment maps] available for30//! you to use.31//!32//! Currently, reflection probes (i.e. environment maps attached to light33//! probes) use binding arrays (also known as bindless textures) and34//! consequently aren't supported on WebGL2 or WebGPU. Reflection probes are35//! also unsupported if GLSL is in use, due to `naga` limitations. Environment36//! maps attached to views are, however, supported on all platforms.37//!38//! [split-sum approximation]: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf39//!40//! [glTF IBL Sampler]: https://github.com/KhronosGroup/glTF-IBL-Sampler41//!42//! [artist-friendly UI]: https://github.com/pcwalton/gltf-ibl-sampler-egui43//!44//! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments4546use bevy_asset::AssetId;47use bevy_ecs::{48query::{QueryData, QueryItem},49system::lifetimeless::Read,50};51use bevy_image::Image;52use bevy_light::{EnvironmentMapLight, ParallaxCorrection};53use bevy_math::{Affine3A, Vec3};54use bevy_render::{55extract_instances::ExtractInstance,56render_asset::RenderAssets,57render_resource::{58binding_types::{self, uniform_buffer},59BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, ShaderStages, TextureSampleType,60TextureView,61},62renderer::{RenderAdapter, RenderDevice},63texture::{FallbackImage, GpuImage},64};6566use core::{num::NonZero, ops::Deref};6768use crate::{69add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform,70RenderLightProbeFlags, MAX_VIEW_LIGHT_PROBES,71};7273use super::{LightProbeComponent, RenderViewLightProbes};7475/// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles.76///77/// This is for use in the render app.78#[derive(Clone, Copy, PartialEq, Eq, Hash)]79pub struct EnvironmentMapIds {80/// The blurry image that represents diffuse radiance surrounding a region.81pub diffuse: AssetId<Image>,82/// The typically-sharper, mipmapped image that represents specular radiance83/// surrounding a region.84pub specular: AssetId<Image>,85}8687/// All the bind group entries necessary for PBR shaders to access the88/// environment maps exposed to a view.89pub(crate) enum RenderViewEnvironmentMapBindGroupEntries<'a> {90/// The version used when binding arrays aren't available on the current91/// platform.92Single {93/// The texture view of the view's diffuse cubemap.94diffuse_texture_view: &'a TextureView,9596/// The texture view of the view's specular cubemap.97specular_texture_view: &'a TextureView,9899/// The sampler used to sample elements of both `diffuse_texture_views` and100/// `specular_texture_views`.101sampler: &'a Sampler,102},103104/// The version used when binding arrays are available on the current105/// platform.106Multiple {107/// A texture view of each diffuse cubemap, in the same order that they are108/// supplied to the view (i.e. in the same order as109/// `binding_index_to_cubemap` in [`RenderViewLightProbes`]).110///111/// This is a vector of `wgpu::TextureView`s. But we don't want to import112/// `wgpu` in this crate, so we refer to it indirectly like this.113diffuse_texture_views: Vec<&'a <TextureView as Deref>::Target>,114115/// As above, but for specular cubemaps.116specular_texture_views: Vec<&'a <TextureView as Deref>::Target>,117118/// The sampler used to sample elements of both `diffuse_texture_views` and119/// `specular_texture_views`.120sampler: &'a Sampler,121},122}123124/// Information about the environment map attached to the view, if any. This is125/// a global environment map that lights everything visible in the view, as126/// opposed to a light probe which affects only a specific area.127pub struct EnvironmentMapViewLightProbeInfo {128/// The index of the diffuse and specular cubemaps in the binding arrays.129pub(crate) cubemap_index: i32,130/// The smallest mip level of the specular cubemap.131pub(crate) smallest_specular_mip_level: u32,132/// The scale factor applied to the diffuse and specular light in the133/// cubemap. This is in units of cd/m² (candela per square meter).134pub(crate) intensity: f32,135/// Whether this lightmap affects the diffuse lighting of lightmapped136/// meshes.137pub(crate) affects_lightmapped_mesh_diffuse: bool,138}139140impl ExtractInstance for EnvironmentMapIds {141type QueryData = Read<EnvironmentMapLight>;142143type QueryFilter = ();144145fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self> {146Some(EnvironmentMapIds {147diffuse: item.diffuse_map.id(),148specular: item.specular_map.id(),149})150}151}152153/// Returns the bind group layout entries for the environment map diffuse and154/// specular binding arrays respectively, in addition to the sampler.155pub(crate) fn get_bind_group_layout_entries(156render_device: &RenderDevice,157render_adapter: &RenderAdapter,158) -> [BindGroupLayoutEntryBuilder; 4] {159let mut texture_cube_binding =160binding_types::texture_cube(TextureSampleType::Float { filterable: true });161if binding_arrays_are_usable(render_device, render_adapter) {162texture_cube_binding =163texture_cube_binding.count(NonZero::<u32>::new(MAX_VIEW_LIGHT_PROBES as _).unwrap());164}165166[167texture_cube_binding,168texture_cube_binding,169binding_types::sampler(SamplerBindingType::Filtering),170uniform_buffer::<EnvironmentMapUniform>(true).visibility(ShaderStages::FRAGMENT),171]172}173174impl<'a> RenderViewEnvironmentMapBindGroupEntries<'a> {175/// Looks up and returns the bindings for the environment map diffuse and176/// specular binding arrays respectively, as well as the sampler.177pub(crate) fn get(178render_view_environment_maps: Option<&RenderViewLightProbes<EnvironmentMapLight>>,179images: &'a RenderAssets<GpuImage>,180fallback_image: &'a FallbackImage,181render_device: &RenderDevice,182render_adapter: &RenderAdapter,183) -> RenderViewEnvironmentMapBindGroupEntries<'a> {184if binding_arrays_are_usable(render_device, render_adapter) {185// Initialize the diffuse and specular texture views with the fallback texture.186let mut diffuse_texture_views = vec![];187let mut specular_texture_views = vec![];188let mut sampler = None;189190if let Some(environment_maps) = render_view_environment_maps {191for &cubemap_id in &environment_maps.binding_index_to_textures {192add_cubemap_texture_view(193&mut diffuse_texture_views,194&mut sampler,195cubemap_id.diffuse,196images,197fallback_image,198);199add_cubemap_texture_view(200&mut specular_texture_views,201&mut sampler,202cubemap_id.specular,203images,204fallback_image,205);206}207}208209// Pad out the bindings to the size of the binding array using fallback210// textures. This is necessary on D3D12 and Metal.211diffuse_texture_views.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);212specular_texture_views213.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);214215return RenderViewEnvironmentMapBindGroupEntries::Multiple {216diffuse_texture_views,217specular_texture_views,218sampler: sampler.unwrap_or(&fallback_image.cube.sampler),219};220}221222if let Some(environment_maps) = render_view_environment_maps223&& let Some(cubemap) = environment_maps.binding_index_to_textures.first()224&& let (Some(diffuse_image), Some(specular_image)) =225(images.get(cubemap.diffuse), images.get(cubemap.specular))226{227return RenderViewEnvironmentMapBindGroupEntries::Single {228diffuse_texture_view: &diffuse_image.texture_view,229specular_texture_view: &specular_image.texture_view,230sampler: &diffuse_image.sampler,231};232}233234RenderViewEnvironmentMapBindGroupEntries::Single {235diffuse_texture_view: &fallback_image.cube.texture_view,236specular_texture_view: &fallback_image.cube.texture_view,237sampler: &fallback_image.cube.sampler,238}239}240}241242impl LightProbeComponent for EnvironmentMapLight {243type AssetId = EnvironmentMapIds;244245// Information needed to render with the environment map attached to the246// view.247type ViewLightProbeInfo = EnvironmentMapViewLightProbeInfo;248249type QueryData = Option<Read<ParallaxCorrection>>;250251fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId> {252if image_assets.get(&self.diffuse_map).is_none()253|| image_assets.get(&self.specular_map).is_none()254{255None256} else {257Some(EnvironmentMapIds {258diffuse: self.diffuse_map.id(),259specular: self.specular_map.id(),260})261}262}263264fn intensity(&self) -> f32 {265self.intensity266}267268fn flags(269&self,270maybe_parallax_correction: &<Self::QueryData as QueryData>::Item<'_, '_>,271) -> RenderLightProbeFlags {272let mut flags = RenderLightProbeFlags::empty();273if self.affects_lightmapped_mesh_diffuse {274flags.insert(RenderLightProbeFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE);275}276if maybe_parallax_correction.is_some_and(|parallax_correction| {277!matches!(*parallax_correction, ParallaxCorrection::None)278}) {279flags.insert(RenderLightProbeFlags::ENABLE_PARALLAX_CORRECTION);280}281flags282}283284fn create_render_view_light_probes(285view_component: Option<&EnvironmentMapLight>,286image_assets: &RenderAssets<GpuImage>,287) -> RenderViewLightProbes<Self> {288let mut render_view_light_probes = RenderViewLightProbes::new();289290// Find the index of the cubemap associated with the view, and determine291// its smallest mip level.292if let Some(EnvironmentMapLight {293diffuse_map: diffuse_map_handle,294specular_map: specular_map_handle,295intensity,296affects_lightmapped_mesh_diffuse,297..298}) = view_component299&& let (Some(_), Some(specular_map)) = (300image_assets.get(diffuse_map_handle),301image_assets.get(specular_map_handle),302)303{304render_view_light_probes.view_light_probe_info =305Some(EnvironmentMapViewLightProbeInfo {306cubemap_index: render_view_light_probes.get_or_insert_cubemap(307&EnvironmentMapIds {308diffuse: diffuse_map_handle.id(),309specular: specular_map_handle.id(),310},311) as i32,312smallest_specular_mip_level: specular_map.texture_descriptor.mip_level_count313- 1,314intensity: *intensity,315affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse,316});317};318319render_view_light_probes320}321322fn get_world_from_light_matrix(&self, original_transform: &Affine3A) -> Affine3A {323// Take the `rotation` field into account.324*original_transform * Affine3A::from_quat(self.rotation)325}326327fn parallax_correction_bounds(328&self,329maybe_parallax_correction: &<Self::QueryData as QueryData>::Item<'_, '_>,330) -> Vec3 {331match *maybe_parallax_correction {332Some(&ParallaxCorrection::Custom(bounds)) => bounds,333Some(&ParallaxCorrection::Auto) => Vec3::splat(0.5),334Some(&ParallaxCorrection::None) | None => Vec3::ZERO,335}336}337}338339impl Default for EnvironmentMapViewLightProbeInfo {340fn default() -> Self {341Self {342cubemap_index: -1,343smallest_specular_mip_level: 0,344intensity: 1.0,345affects_lightmapped_mesh_diffuse: true,346}347}348}349350351