Path: blob/main/crates/bevy_pbr/src/light_probe/environment_map.rs
6604 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::{query::QueryItem, system::lifetimeless::Read};48use bevy_image::Image;49use bevy_light::EnvironmentMapLight;50use bevy_render::{51extract_instances::ExtractInstance,52render_asset::RenderAssets,53render_resource::{54binding_types::{self, uniform_buffer},55BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, ShaderStages, TextureSampleType,56TextureView,57},58renderer::{RenderAdapter, RenderDevice},59texture::{FallbackImage, GpuImage},60};6162use core::{num::NonZero, ops::Deref};6364use crate::{65add_cubemap_texture_view, binding_arrays_are_usable, EnvironmentMapUniform,66MAX_VIEW_LIGHT_PROBES,67};6869use super::{LightProbeComponent, RenderViewLightProbes};7071/// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles.72///73/// This is for use in the render app.74#[derive(Clone, Copy, PartialEq, Eq, Hash)]75pub struct EnvironmentMapIds {76/// The blurry image that represents diffuse radiance surrounding a region.77pub(crate) diffuse: AssetId<Image>,78/// The typically-sharper, mipmapped image that represents specular radiance79/// surrounding a region.80pub(crate) specular: AssetId<Image>,81}8283/// All the bind group entries necessary for PBR shaders to access the84/// environment maps exposed to a view.85pub(crate) enum RenderViewEnvironmentMapBindGroupEntries<'a> {86/// The version used when binding arrays aren't available on the current87/// platform.88Single {89/// The texture view of the view's diffuse cubemap.90diffuse_texture_view: &'a TextureView,9192/// The texture view of the view's specular cubemap.93specular_texture_view: &'a TextureView,9495/// The sampler used to sample elements of both `diffuse_texture_views` and96/// `specular_texture_views`.97sampler: &'a Sampler,98},99100/// The version used when binding arrays are available on the current101/// platform.102Multiple {103/// A texture view of each diffuse cubemap, in the same order that they are104/// supplied to the view (i.e. in the same order as105/// `binding_index_to_cubemap` in [`RenderViewLightProbes`]).106///107/// This is a vector of `wgpu::TextureView`s. But we don't want to import108/// `wgpu` in this crate, so we refer to it indirectly like this.109diffuse_texture_views: Vec<&'a <TextureView as Deref>::Target>,110111/// As above, but for specular cubemaps.112specular_texture_views: Vec<&'a <TextureView as Deref>::Target>,113114/// The sampler used to sample elements of both `diffuse_texture_views` and115/// `specular_texture_views`.116sampler: &'a Sampler,117},118}119120/// Information about the environment map attached to the view, if any. This is121/// a global environment map that lights everything visible in the view, as122/// opposed to a light probe which affects only a specific area.123pub struct EnvironmentMapViewLightProbeInfo {124/// The index of the diffuse and specular cubemaps in the binding arrays.125pub(crate) cubemap_index: i32,126/// The smallest mip level of the specular cubemap.127pub(crate) smallest_specular_mip_level: u32,128/// The scale factor applied to the diffuse and specular light in the129/// cubemap. This is in units of cd/m² (candela per square meter).130pub(crate) intensity: f32,131/// Whether this lightmap affects the diffuse lighting of lightmapped132/// meshes.133pub(crate) affects_lightmapped_mesh_diffuse: bool,134}135136impl ExtractInstance for EnvironmentMapIds {137type QueryData = Read<EnvironmentMapLight>;138139type QueryFilter = ();140141fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self> {142Some(EnvironmentMapIds {143diffuse: item.diffuse_map.id(),144specular: item.specular_map.id(),145})146}147}148149/// Returns the bind group layout entries for the environment map diffuse and150/// specular binding arrays respectively, in addition to the sampler.151pub(crate) fn get_bind_group_layout_entries(152render_device: &RenderDevice,153render_adapter: &RenderAdapter,154) -> [BindGroupLayoutEntryBuilder; 4] {155let mut texture_cube_binding =156binding_types::texture_cube(TextureSampleType::Float { filterable: true });157if binding_arrays_are_usable(render_device, render_adapter) {158texture_cube_binding =159texture_cube_binding.count(NonZero::<u32>::new(MAX_VIEW_LIGHT_PROBES as _).unwrap());160}161162[163texture_cube_binding,164texture_cube_binding,165binding_types::sampler(SamplerBindingType::Filtering),166uniform_buffer::<EnvironmentMapUniform>(true).visibility(ShaderStages::FRAGMENT),167]168}169170impl<'a> RenderViewEnvironmentMapBindGroupEntries<'a> {171/// Looks up and returns the bindings for the environment map diffuse and172/// specular binding arrays respectively, as well as the sampler.173pub(crate) fn get(174render_view_environment_maps: Option<&RenderViewLightProbes<EnvironmentMapLight>>,175images: &'a RenderAssets<GpuImage>,176fallback_image: &'a FallbackImage,177render_device: &RenderDevice,178render_adapter: &RenderAdapter,179) -> RenderViewEnvironmentMapBindGroupEntries<'a> {180if binding_arrays_are_usable(render_device, render_adapter) {181let mut diffuse_texture_views = vec![];182let mut specular_texture_views = vec![];183let mut sampler = None;184185if let Some(environment_maps) = render_view_environment_maps {186for &cubemap_id in &environment_maps.binding_index_to_textures {187add_cubemap_texture_view(188&mut diffuse_texture_views,189&mut sampler,190cubemap_id.diffuse,191images,192fallback_image,193);194add_cubemap_texture_view(195&mut specular_texture_views,196&mut sampler,197cubemap_id.specular,198images,199fallback_image,200);201}202}203204// Pad out the bindings to the size of the binding array using fallback205// textures. This is necessary on D3D12 and Metal.206diffuse_texture_views.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);207specular_texture_views208.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);209210return RenderViewEnvironmentMapBindGroupEntries::Multiple {211diffuse_texture_views,212specular_texture_views,213sampler: sampler.unwrap_or(&fallback_image.cube.sampler),214};215}216217if let Some(environment_maps) = render_view_environment_maps218&& let Some(cubemap) = environment_maps.binding_index_to_textures.first()219&& let (Some(diffuse_image), Some(specular_image)) =220(images.get(cubemap.diffuse), images.get(cubemap.specular))221{222return RenderViewEnvironmentMapBindGroupEntries::Single {223diffuse_texture_view: &diffuse_image.texture_view,224specular_texture_view: &specular_image.texture_view,225sampler: &diffuse_image.sampler,226};227}228229RenderViewEnvironmentMapBindGroupEntries::Single {230diffuse_texture_view: &fallback_image.cube.texture_view,231specular_texture_view: &fallback_image.cube.texture_view,232sampler: &fallback_image.cube.sampler,233}234}235}236237impl LightProbeComponent for EnvironmentMapLight {238type AssetId = EnvironmentMapIds;239240// Information needed to render with the environment map attached to the241// view.242type ViewLightProbeInfo = EnvironmentMapViewLightProbeInfo;243244fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId> {245if image_assets.get(&self.diffuse_map).is_none()246|| image_assets.get(&self.specular_map).is_none()247{248None249} else {250Some(EnvironmentMapIds {251diffuse: self.diffuse_map.id(),252specular: self.specular_map.id(),253})254}255}256257fn intensity(&self) -> f32 {258self.intensity259}260261fn affects_lightmapped_mesh_diffuse(&self) -> bool {262self.affects_lightmapped_mesh_diffuse263}264265fn create_render_view_light_probes(266view_component: Option<&EnvironmentMapLight>,267image_assets: &RenderAssets<GpuImage>,268) -> RenderViewLightProbes<Self> {269let mut render_view_light_probes = RenderViewLightProbes::new();270271// Find the index of the cubemap associated with the view, and determine272// its smallest mip level.273if let Some(EnvironmentMapLight {274diffuse_map: diffuse_map_handle,275specular_map: specular_map_handle,276intensity,277affects_lightmapped_mesh_diffuse,278..279}) = view_component280&& let (Some(_), Some(specular_map)) = (281image_assets.get(diffuse_map_handle),282image_assets.get(specular_map_handle),283)284{285render_view_light_probes.view_light_probe_info = EnvironmentMapViewLightProbeInfo {286cubemap_index: render_view_light_probes.get_or_insert_cubemap(&EnvironmentMapIds {287diffuse: diffuse_map_handle.id(),288specular: specular_map_handle.id(),289}) as i32,290smallest_specular_mip_level: specular_map.mip_level_count - 1,291intensity: *intensity,292affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse,293};294};295296render_view_light_probes297}298}299300impl Default for EnvironmentMapViewLightProbeInfo {301fn default() -> Self {302Self {303cubemap_index: -1,304smallest_specular_mip_level: 0,305intensity: 1.0,306affects_lightmapped_mesh_diffuse: true,307}308}309}310311312