Revert "Implement minimal reflection probes. (#10057)" (#11307)

# Objective

- Fix working on macOS, iOS, Android on main 
- Fixes #11281 
- Fixes #11282 
- Fixes #11283 
- Fixes #11299

## Solution

- Revert #10057
This commit is contained in:
François 2024-01-12 21:41:51 +01:00 committed by GitHub
parent 64a15f1b10
commit 3d996639a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 249 additions and 1523 deletions

View file

@ -2456,17 +2456,6 @@ name = "fallback_image"
path = "examples/shader/fallback_image.rs"
doc-scrape-examples = true
[[example]]
name = "reflection_probes"
path = "examples/3d/reflection_probes.rs"
doc-scrape-examples = true
[package.metadata.example.reflection_probes]
name = "Reflection Probes"
description = "Demonstrates reflection probes"
category = "3D Rendering"
wasm = false
[package.metadata.example.fallback_image]
hidden = true

Binary file not shown.

View file

@ -7,8 +7,8 @@
@group(0) @binding(3) var dt_lut_texture: texture_3d<f32>;
@group(0) @binding(4) var dt_lut_sampler: sampler;
#else
@group(0) @binding(16) var dt_lut_texture: texture_3d<f32>;
@group(0) @binding(17) var dt_lut_sampler: sampler;
@group(0) @binding(15) var dt_lut_texture: texture_3d<f32>;
@group(0) @binding(16) var dt_lut_sampler: sampler;
#endif
fn sample_current_lut(p: vec3<f32>) -> vec3<f32> {

View file

@ -58,10 +58,7 @@ symphonia-vorbis = ["bevy_audio/symphonia-vorbis"]
symphonia-wav = ["bevy_audio/symphonia-wav"]
# Shader formats
shader_format_glsl = [
"bevy_render/shader_format_glsl",
"bevy_pbr?/shader_format_glsl",
]
shader_format_glsl = ["bevy_render/shader_format_glsl"]
shader_format_spirv = ["bevy_render/shader_format_spirv"]
serialize = [

View file

@ -10,7 +10,6 @@ keywords = ["bevy"]
[features]
webgl = []
shader_format_glsl = ["naga_oil/glsl"]
pbr_transmission_textures = []
[dependencies]
@ -35,17 +34,8 @@ fixedbitset = "0.4"
# direct dependency required for derive macro
bytemuck = { version = "1", features = ["derive"] }
radsort = "0.1"
smallvec = "1.6"
naga_oil = "0.11"
thread_local = "1.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
naga_oil = { version = "0.11" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Omit the `glsl` feature in non-WebAssembly by default.
naga_oil = { version = "0.11", default-features = false, features = [
"test_shader",
] }
[lints]
workspace = true

View file

@ -1,7 +1,4 @@
use crate::{
environment_map::RenderViewEnvironmentMaps, MeshPipeline, MeshViewBindGroup,
ScreenSpaceAmbientOcclusionSettings, ViewLightProbesUniformOffset,
};
use crate::{MeshPipeline, MeshViewBindGroup, ScreenSpaceAmbientOcclusionSettings};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_core_pipeline::{
@ -17,17 +14,25 @@ use bevy_render::{
extract_component::{
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
},
render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
render_resource::binding_types::uniform_buffer,
render_resource::*,
render_asset::RenderAssets,
render_graph::{NodeRunError, RenderGraphContext, ViewNode, ViewNodeRunner},
render_resource::{
binding_types::uniform_buffer, Operations, PipelineCache, RenderPassDescriptor,
},
renderer::{RenderContext, RenderDevice},
texture::BevyDefault,
view::{ExtractedView, ViewTarget, ViewUniformOffset},
Render, RenderApp, RenderSet,
texture::Image,
view::{ViewTarget, ViewUniformOffset},
Render, RenderSet,
};
use bevy_render::{
render_graph::RenderGraphApp, render_resource::*, texture::BevyDefault, view::ExtractedView,
RenderApp,
};
use crate::{
MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset, ViewLightsUniformOffset,
EnvironmentMapLight, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset,
ViewLightsUniformOffset,
};
pub struct DeferredPbrLightingPlugin;
@ -146,7 +151,6 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
&'static ViewUniformOffset,
&'static ViewLightsUniformOffset,
&'static ViewFogUniformOffset,
&'static ViewLightProbesUniformOffset,
&'static MeshViewBindGroup,
&'static ViewTarget,
&'static DeferredLightingIdDepthTexture,
@ -161,7 +165,6 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
view_uniform_offset,
view_lights_offset,
view_fog_offset,
view_light_probes_offset,
mesh_view_bind_group,
target,
deferred_lighting_id_depth_texture,
@ -215,7 +218,6 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
view_uniform_offset.offset,
view_lights_offset.offset,
view_fog_offset.offset,
**view_light_probes_offset,
],
);
render_pass.set_bind_group(1, &bind_group_1, &[]);
@ -401,6 +403,7 @@ pub fn prepare_deferred_lighting_pipelines(
&ExtractedView,
Option<&Tonemapping>,
Option<&DebandDither>,
Option<&EnvironmentMapLight>,
Option<&ShadowFilteringMethod>,
Has<ScreenSpaceAmbientOcclusionSettings>,
(
@ -408,20 +411,20 @@ pub fn prepare_deferred_lighting_pipelines(
Has<DepthPrepass>,
Has<MotionVectorPrepass>,
),
Has<RenderViewEnvironmentMaps>,
),
With<DeferredPrepass>,
>,
images: Res<RenderAssets<Image>>,
) {
for (
entity,
view,
tonemapping,
dither,
environment_map,
shadow_filter_method,
ssao,
(normal_prepass, depth_prepass, motion_vector_prepass),
has_environment_maps,
) in &views
{
let mut view_key = MeshPipelineKey::from_hdr(view.hdr);
@ -468,10 +471,11 @@ pub fn prepare_deferred_lighting_pipelines(
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
}
// We don't need to check to see whether the environment map is loaded
// because [`gather_light_probes`] already checked that for us before
// adding the [`RenderViewEnvironmentMaps`] component.
if has_environment_maps {
let environment_map_loaded = match environment_map {
Some(environment_map) => environment_map.is_loaded(&images),
None => false,
};
if environment_map_loaded {
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
}

View file

@ -0,0 +1,50 @@
#define_import_path bevy_pbr::environment_map
#import bevy_pbr::mesh_view_bindings as bindings;
struct EnvironmentMapLight {
diffuse: vec3<f32>,
specular: vec3<f32>,
};
fn environment_map_light(
perceptual_roughness: f32,
roughness: f32,
diffuse_color: vec3<f32>,
NdotV: f32,
f_ab: vec2<f32>,
N: vec3<f32>,
R: vec3<f32>,
F0: vec3<f32>,
) -> EnvironmentMapLight {
// Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
// Technically we could use textureNumLevels(environment_map_specular) - 1 here, but we use a uniform
// because textureNumLevels() does not work on WebGL2
let radiance_level = perceptual_roughness * f32(bindings::lights.environment_map_smallest_specular_mip_level);
let irradiance = textureSampleLevel(bindings::environment_map_diffuse, bindings::environment_map_sampler, vec3(N.xy, -N.z), 0.0).rgb;
let radiance = textureSampleLevel(bindings::environment_map_specular, bindings::environment_map_sampler, vec3(R.xy, -R.z), radiance_level).rgb;
// No real world material has specular values under 0.02, so we use this range as a
// "pre-baked specular occlusion" that extinguishes the fresnel term, for artistic control.
// See: https://google.github.io/filament/Filament.html#specularocclusion
let specular_occlusion = saturate(dot(F0, vec3(50.0 * 0.33)));
// Multiscattering approximation: https://www.jcgt.org/published/0008/01/03/paper.pdf
// Useful reference: https://bruop.github.io/ibl
let Fr = max(vec3(1.0 - roughness), F0) - F0;
let kS = F0 + Fr * pow(1.0 - NdotV, 5.0);
let Ess = f_ab.x + f_ab.y;
let FssEss = kS * Ess * specular_occlusion;
let Ems = 1.0 - Ess;
let Favg = F0 + (1.0 - F0) / 21.0;
let Fms = FssEss * Favg / (1.0 - Ems * Favg);
let FmsEms = Fms * Ems;
let Edss = 1.0 - (FssEss + FmsEms);
let kD = diffuse_color * Edss;
var out: EnvironmentMapLight;
out.diffuse = (FmsEms + kD) * irradiance;
out.specular = FssEss * radiance;
return out;
}

View file

@ -0,0 +1,91 @@
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Handle};
use bevy_core_pipeline::prelude::Camera3d;
use bevy_ecs::{prelude::Component, query::With};
use bevy_reflect::Reflect;
use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_asset::RenderAssets,
render_resource::{
binding_types::{sampler, texture_cube},
*,
},
texture::{FallbackImageCubemap, Image},
};
pub const ENVIRONMENT_MAP_SHADER_HANDLE: Handle<Shader> =
Handle::weak_from_u128(154476556247605696);
pub struct EnvironmentMapPlugin;
impl Plugin for EnvironmentMapPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
ENVIRONMENT_MAP_SHADER_HANDLE,
"environment_map.wgsl",
Shader::from_wgsl
);
app.register_type::<EnvironmentMapLight>()
.add_plugins(ExtractComponentPlugin::<EnvironmentMapLight>::default());
}
}
/// Environment map based ambient lighting representing light from distant scenery.
///
/// When added to a 3D camera, this component adds indirect light
/// to every point of the scene (including inside, enclosed areas) based on
/// an environment cubemap texture. This is similar to [`crate::AmbientLight`], but
/// higher quality, and is intended for outdoor scenes.
///
/// The environment map must be prefiltered into a diffuse and specular cubemap based on the
/// [split-sum approximation](https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf).
///
/// To prefilter your environment map, you can use `KhronosGroup`'s [glTF-IBL-Sampler](https://github.com/KhronosGroup/glTF-IBL-Sampler).
/// The diffuse map uses the Lambertian distribution, and the specular map uses the GGX distribution.
///
/// `KhronosGroup` also has several prefiltered environment maps that can be found [here](https://github.com/KhronosGroup/glTF-Sample-Environments).
#[derive(Component, Reflect, Clone, ExtractComponent)]
#[extract_component_filter(With<Camera3d>)]
pub struct EnvironmentMapLight {
pub diffuse_map: Handle<Image>,
pub specular_map: Handle<Image>,
}
impl EnvironmentMapLight {
/// Whether or not all textures necessary to use the environment map
/// have been loaded by the asset server.
pub fn is_loaded(&self, images: &RenderAssets<Image>) -> bool {
images.get(&self.diffuse_map).is_some() && images.get(&self.specular_map).is_some()
}
}
pub fn get_bindings<'a>(
environment_map_light: Option<&EnvironmentMapLight>,
images: &'a RenderAssets<Image>,
fallback_image_cubemap: &'a FallbackImageCubemap,
) -> (&'a TextureView, &'a TextureView, &'a Sampler) {
let (diffuse_map, specular_map) = match (
environment_map_light.and_then(|env_map| images.get(&env_map.diffuse_map)),
environment_map_light.and_then(|env_map| images.get(&env_map.specular_map)),
) {
(Some(diffuse_map), Some(specular_map)) => {
(&diffuse_map.texture_view, &specular_map.texture_view)
}
_ => (
&fallback_image_cubemap.texture_view,
&fallback_image_cubemap.texture_view,
),
};
(diffuse_map, specular_map, &fallback_image_cubemap.sampler)
}
pub fn get_bind_group_layout_entries() -> [BindGroupLayoutEntryBuilder; 3] {
[
texture_cube(TextureSampleType::Float { filterable: true }),
texture_cube(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
]
}

View file

@ -3,10 +3,10 @@ pub mod wireframe;
mod alpha;
mod bundle;
pub mod deferred;
mod environment_map;
mod extended_material;
mod fog;
mod light;
mod light_probe;
mod lightmap;
mod material;
mod parallax;
@ -17,10 +17,10 @@ mod ssao;
pub use alpha::*;
pub use bundle::*;
pub use environment_map::EnvironmentMapLight;
pub use extended_material::*;
pub use fog::*;
pub use light::*;
pub use light_probe::*;
pub use lightmap::*;
pub use material::*;
pub use parallax::*;
@ -37,12 +37,9 @@ pub mod prelude {
DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle,
SpotLightBundle,
},
environment_map::EnvironmentMapLight,
fog::{FogFalloff, FogSettings},
light::{AmbientLight, DirectionalLight, PointLight, SpotLight},
light_probe::{
environment_map::{EnvironmentMapLight, ReflectionProbeBundle},
LightProbe,
},
material::{Material, MaterialPlugin},
parallax::ParallaxMappingMethod,
pbr_material::StandardMaterial,
@ -74,6 +71,7 @@ use bevy_render::{
ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::TransformSystem;
use environment_map::EnvironmentMapPlugin;
use crate::deferred::DeferredPbrLightingPlugin;
@ -257,12 +255,12 @@ impl Plugin for PbrPlugin {
..Default::default()
},
ScreenSpaceAmbientOcclusionPlugin,
EnvironmentMapPlugin,
ExtractResourcePlugin::<AmbientLight>::default(),
FogPlugin,
ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default(),
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
LightmapPlugin,
LightProbePlugin,
))
.configure_sets(
PostUpdate,

View file

@ -1,405 +0,0 @@
//! Environment maps and reflection probes.
//!
//! An *environment map* consists of a pair of diffuse and specular cubemaps
//! that together reflect the static surrounding area of a region in space. When
//! available, the PBR shader uses these to apply diffuse light and calculate
//! specular reflections.
//!
//! Environment maps come in two flavors, depending on what other components the
//! entities they're attached to have:
//!
//! 1. If attached to a view, they represent the objects located a very far
//! distance from the view, in a similar manner to a skybox. Essentially, these
//! *view environment maps* represent a higher-quality replacement for
//! [`crate::AmbientLight`] for outdoor scenes. The indirect light from such
//! environment maps are added to every point of the scene, including
//! interior enclosed areas.
//!
//! 2. If attached to a [`LightProbe`], environment maps represent the immediate
//! surroundings of a specific location in the scene. These types of
//! environment maps are known as *reflection probes*.
//! [`ReflectionProbeBundle`] is available as a mechanism to conveniently add
//! these to a scene.
//!
//! Typically, environment maps are static (i.e. "baked", calculated ahead of
//! time) and so only reflect fixed static geometry. The environment maps must
//! be pre-filtered into a pair of cubemaps, one for the diffuse component and
//! one for the specular component, according to the [split-sum approximation].
//! To pre-filter your environment map, you can use the [glTF IBL Sampler] or
//! its [artist-friendly UI]. The diffuse map uses the Lambertian distribution,
//! while the specular map uses the GGX distribution.
//!
//! The Khronos Group has [several pre-filtered environment maps] available for
//! you to use.
//!
//! Currently, reflection probes (i.e. environment maps attached to light
//! probes) use binding arrays (also known as bindless textures) and
//! consequently aren't supported on WebGL2 or WebGPU. Reflection probes are
//! also unsupported if GLSL is in use, due to `naga` limitations. Environment
//! maps attached to views are, however, supported on all platforms.
//!
//! [split-sum approximation]: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
//!
//! [glTF IBL Sampler]: https://github.com/KhronosGroup/glTF-IBL-Sampler
//!
//! [artist-friendly UI]: https://github.com/pcwalton/gltf-ibl-sampler-egui
//!
//! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments
use bevy_asset::{AssetId, Handle};
use bevy_ecs::{
bundle::Bundle, component::Component, query::QueryItem, system::lifetimeless::Read,
};
use bevy_reflect::Reflect;
use bevy_render::{
extract_instances::ExtractInstance,
prelude::SpatialBundle,
render_asset::RenderAssets,
render_resource::{
binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, Shader,
TextureSampleType, TextureView,
},
texture::{FallbackImage, Image},
};
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
use bevy_utils::HashMap;
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
use std::num::NonZeroU32;
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
use std::ops::Deref;
use crate::LightProbe;
/// A handle to the environment map helper shader.
pub const ENVIRONMENT_MAP_SHADER_HANDLE: Handle<Shader> =
Handle::weak_from_u128(154476556247605696);
/// A pair of cubemap textures that represent the surroundings of a specific
/// area in space.
///
/// See [`crate::environment_map`] for detailed information.
#[derive(Clone, Component, Reflect)]
pub struct EnvironmentMapLight {
/// The blurry image that represents diffuse radiance surrounding a region.
pub diffuse_map: Handle<Image>,
/// The typically-sharper, mipmapped image that represents specular radiance
/// surrounding a region.
pub specular_map: Handle<Image>,
}
/// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles.
///
/// This is for use in the render app.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct EnvironmentMapIds {
/// The blurry image that represents diffuse radiance surrounding a region.
pub(crate) diffuse: AssetId<Image>,
/// The typically-sharper, mipmapped image that represents specular radiance
/// surrounding a region.
pub(crate) specular: AssetId<Image>,
}
/// A bundle that contains everything needed to make an entity a reflection
/// probe.
///
/// A reflection probe is a type of environment map that specifies the light
/// surrounding a region in space. For more information, see
/// [`crate::environment_map`].
#[derive(Bundle)]
pub struct ReflectionProbeBundle {
/// Contains a transform that specifies the position of this reflection probe in space.
pub spatial: SpatialBundle,
/// Marks this environment map as a light probe.
pub light_probe: LightProbe,
/// The cubemaps that make up this environment map.
pub environment_map: EnvironmentMapLight,
}
/// A component, part of the render world, that stores the mapping from
/// environment map ID to texture index in the diffuse and specular binding
/// arrays.
///
/// Cubemap textures belonging to environment maps are collected into binding
/// arrays, and the index of each texture is presented to the shader for runtime
/// lookup.
///
/// This component is attached to each view in the render world, because each
/// view may have a different set of cubemaps that it considers and therefore
/// cubemap indices are per-view.
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
#[derive(Component, Default)]
pub struct RenderViewEnvironmentMaps {
/// The list of environment maps presented to the shader, in order.
binding_index_to_cubemap: Vec<EnvironmentMapIds>,
/// The reverse of `binding_index_to_cubemap`: a map from the environment
/// map IDs to the index in `binding_index_to_cubemap`.
cubemap_to_binding_index: HashMap<EnvironmentMapIds, u32>,
}
/// A component, part of the render world, that stores the ID of the environment
/// map attached to each view.
///
/// This is a simplified version of the structure used when binding arrays are
/// not available on the current platform.
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
#[derive(Component, Default)]
pub struct RenderViewEnvironmentMaps {
/// The environment map attached to the view, if any.
cubemap: Option<EnvironmentMapIds>,
}
/// All the bind group entries necessary for PBR shaders to access the
/// environment maps exposed to a view.
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
pub(crate) struct RenderViewBindGroupEntries<'a> {
/// A texture view of each diffuse cubemap, in the same order that they are
/// supplied to the view (i.e. in the same order as
/// `binding_index_to_cubemap` in [`RenderViewEnvironmentMaps`]).
///
/// This is a vector of `wgpu::TextureView`s. But we don't want to import
/// `wgpu` in this crate, so we refer to it indirectly like this.
diffuse_texture_views: Vec<&'a <TextureView as Deref>::Target>,
/// As above, but for specular cubemaps.
specular_texture_views: Vec<&'a <TextureView as Deref>::Target>,
/// The sampler used to sample elements of both `diffuse_texture_views` and
/// `specular_texture_views`.
pub(crate) sampler: &'a Sampler,
}
/// All the bind group entries necessary for PBR shaders to access the
/// environment maps exposed to a view.
///
/// This is the version used when binding arrays are not available on the
/// current platform.
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
pub(crate) struct RenderViewBindGroupEntries<'a> {
/// The texture view of the view's diffuse cubemap.
diffuse_texture_view: &'a TextureView,
/// The texture view of the view's specular cubemap.
specular_texture_view: &'a TextureView,
/// The sampler used to sample elements of both `diffuse_texture_view` and
/// `specular_texture_view`.
pub(crate) sampler: &'a Sampler,
}
impl ExtractInstance for EnvironmentMapIds {
type Data = Read<EnvironmentMapLight>;
type Filter = ();
fn extract(item: QueryItem<'_, Self::Data>) -> Option<Self> {
Some(EnvironmentMapIds {
diffuse: item.diffuse_map.id(),
specular: item.specular_map.id(),
})
}
}
impl RenderViewEnvironmentMaps {
pub(crate) fn new() -> Self {
Self::default()
}
}
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
impl RenderViewEnvironmentMaps {
/// Whether there are no environment maps associated with the view.
pub(crate) fn is_empty(&self) -> bool {
self.binding_index_to_cubemap.is_empty()
}
/// Adds a cubemap to the list of bindings, if it wasn't there already, and
/// returns its index within that list.
pub(crate) fn get_or_insert_cubemap(&mut self, cubemap_id: &EnvironmentMapIds) -> u32 {
*self
.cubemap_to_binding_index
.entry(*cubemap_id)
.or_insert_with(|| {
let index = self.binding_index_to_cubemap.len() as u32;
self.binding_index_to_cubemap.push(*cubemap_id);
index
})
}
}
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
impl RenderViewEnvironmentMaps {
/// Returns true if there is no environment map for this view or false if
/// there is such an environment map.
pub(crate) fn is_empty(&self) -> bool {
self.cubemap.is_none()
}
/// Sets the environment map attached to this view, replacing the previous
/// one if any.
pub(crate) fn get_or_insert_cubemap(&mut self, cubemap_id: &EnvironmentMapIds) -> u32 {
self.cubemap = Some(*cubemap_id);
0
}
}
/// Returns the bind group layout entries for the environment map diffuse and
/// specular binding arrays respectively, in addition to the sampler.
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
pub(crate) fn get_bind_group_layout_entries() -> [BindGroupLayoutEntryBuilder; 3] {
use crate::MAX_VIEW_REFLECTION_PROBES;
[
binding_types::texture_cube(TextureSampleType::Float { filterable: true })
.count(NonZeroU32::new(MAX_VIEW_REFLECTION_PROBES as _).unwrap()),
binding_types::texture_cube(TextureSampleType::Float { filterable: true })
.count(NonZeroU32::new(MAX_VIEW_REFLECTION_PROBES as _).unwrap()),
binding_types::sampler(SamplerBindingType::Filtering),
]
}
/// Returns the bind group layout entries for the environment map diffuse and
/// specular textures respectively, in addition to the sampler.
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
pub(crate) fn get_bind_group_layout_entries() -> [BindGroupLayoutEntryBuilder; 3] {
[
binding_types::texture_cube(TextureSampleType::Float { filterable: true }),
binding_types::texture_cube(TextureSampleType::Float { filterable: true }),
binding_types::sampler(SamplerBindingType::Filtering),
]
}
impl<'a> RenderViewBindGroupEntries<'a> {
/// Looks up and returns the bindings for the environment map diffuse and
/// specular binding arrays respectively, as well as the sampler.
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
pub(crate) fn get(
render_view_environment_maps: Option<&RenderViewEnvironmentMaps>,
images: &'a RenderAssets<Image>,
fallback_image: &'a FallbackImage,
) -> RenderViewBindGroupEntries<'a> {
use crate::MAX_VIEW_REFLECTION_PROBES;
let mut diffuse_texture_views = vec![];
let mut specular_texture_views = vec![];
let mut sampler = None;
if let Some(environment_maps) = render_view_environment_maps {
for &cubemap_id in &environment_maps.binding_index_to_cubemap {
add_texture_view(
&mut diffuse_texture_views,
&mut sampler,
cubemap_id.diffuse,
images,
fallback_image,
);
add_texture_view(
&mut specular_texture_views,
&mut sampler,
cubemap_id.specular,
images,
fallback_image,
);
}
}
// Pad out the bindings to the size of the binding array using fallback
// textures. This is necessary on D3D12.
diffuse_texture_views.resize(
MAX_VIEW_REFLECTION_PROBES,
&*fallback_image.cube.texture_view,
);
specular_texture_views.resize(
MAX_VIEW_REFLECTION_PROBES,
&*fallback_image.cube.texture_view,
);
RenderViewBindGroupEntries {
diffuse_texture_views,
specular_texture_views,
sampler: sampler.unwrap_or(&fallback_image.cube.sampler),
}
}
/// Looks up and returns the bindings for the environment map diffuse and
/// specular bindings respectively, as well as the sampler.
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
pub(crate) fn get(
render_view_environment_maps: Option<&RenderViewEnvironmentMaps>,
images: &'a RenderAssets<Image>,
fallback_image: &'a FallbackImage,
) -> RenderViewBindGroupEntries<'a> {
if let Some(&RenderViewEnvironmentMaps {
cubemap: Some(ref cubemap),
}) = render_view_environment_maps
{
if let (Some(diffuse_image), Some(specular_image)) =
(images.get(cubemap.diffuse), images.get(cubemap.specular))
{
return RenderViewBindGroupEntries {
diffuse_texture_view: &diffuse_image.texture_view,
specular_texture_view: &specular_image.texture_view,
sampler: &diffuse_image.sampler,
};
}
}
RenderViewBindGroupEntries {
diffuse_texture_view: &fallback_image.cube.texture_view,
specular_texture_view: &fallback_image.cube.texture_view,
sampler: &fallback_image.cube.sampler,
}
}
}
/// Adds a diffuse or specular texture view to the `texture_views` list, and
/// populates `sampler` if this is the first such view.
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
fn add_texture_view<'a>(
texture_views: &mut Vec<&'a <TextureView as Deref>::Target>,
sampler: &mut Option<&'a Sampler>,
image_id: AssetId<Image>,
images: &'a RenderAssets<Image>,
fallback_image: &'a FallbackImage,
) {
match images.get(image_id) {
None => {
// Use the fallback image if the cubemap isn't loaded yet.
texture_views.push(&*fallback_image.cube.texture_view);
}
Some(image) => {
// If this is the first texture view, populate `sampler`.
if sampler.is_none() {
*sampler = Some(&image.sampler);
}
texture_views.push(&*image.texture_view);
}
}
}
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
impl<'a> RenderViewBindGroupEntries<'a> {
/// Returns a list of texture views of each diffuse cubemap, in binding
/// order.
pub(crate) fn diffuse_texture_views(&'a self) -> &'a [&'a <TextureView as Deref>::Target] {
self.diffuse_texture_views.as_slice()
}
/// Returns a list of texture views of each specular cubemap, in binding
/// order.
pub(crate) fn specular_texture_views(&'a self) -> &'a [&'a <TextureView as Deref>::Target] {
self.specular_texture_views.as_slice()
}
}
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
impl<'a> RenderViewBindGroupEntries<'a> {
/// Returns the texture view corresponding to the view's diffuse cubemap.
pub(crate) fn diffuse_texture_views(&self) -> &'a TextureView {
self.diffuse_texture_view
}
/// Returns the texture view corresponding to the view's specular cubemap.
pub(crate) fn specular_texture_views(&self) -> &'a TextureView {
self.specular_texture_view
}
}

View file

@ -1,171 +0,0 @@
#define_import_path bevy_pbr::environment_map
#import bevy_pbr::mesh_view_bindings as bindings
#import bevy_pbr::mesh_view_bindings::light_probes
struct EnvironmentMapLight {
diffuse: vec3<f32>,
specular: vec3<f32>,
};
struct EnvironmentMapRadiances {
irradiance: vec3<f32>,
radiance: vec3<f32>,
}
// Define two versions of this function, one for the case in which there are
// multiple light probes and one for the case in which only the view light probe
// is present.
#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY
fn compute_radiances(
perceptual_roughness: f32,
N: vec3<f32>,
R: vec3<f32>,
world_position: vec3<f32>,
) -> EnvironmentMapRadiances {
var radiances: EnvironmentMapRadiances;
// Search for a reflection probe that contains the fragment.
//
// TODO: Interpolate between multiple reflection probes.
var cubemap_index: i32 = -1;
for (var reflection_probe_index: i32 = 0;
reflection_probe_index < light_probes.reflection_probe_count;
reflection_probe_index += 1) {
let reflection_probe = light_probes.reflection_probes[reflection_probe_index];
// Unpack the inverse transform.
let inverse_transpose_transform = mat4x4<f32>(
reflection_probe.inverse_transpose_transform[0],
reflection_probe.inverse_transpose_transform[1],
reflection_probe.inverse_transpose_transform[2],
vec4<f32>(0.0, 0.0, 0.0, 1.0));
let inverse_transform = transpose(inverse_transpose_transform);
// Check to see if the transformed point is inside the unit cube
// centered at the origin.
let probe_space_pos = (inverse_transform * vec4<f32>(world_position, 1.0)).xyz;
if (all(abs(probe_space_pos) <= vec3(0.5))) {
cubemap_index = reflection_probe.cubemap_index;
break;
}
}
// If we didn't find a reflection probe, use the view environment map if applicable.
if (cubemap_index < 0) {
cubemap_index = light_probes.view_cubemap_index;
}
// If there's no cubemap, bail out.
if (cubemap_index < 0) {
radiances.irradiance = vec3(0.0);
radiances.radiance = vec3(0.0);
return radiances;
}
// Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
let radiance_level = perceptual_roughness * f32(textureNumLevels(bindings::specular_environment_maps[cubemap_index]) - 1u);
#ifndef LIGHTMAP
radiances.irradiance = textureSampleLevel(
bindings::diffuse_environment_maps[cubemap_index],
bindings::environment_map_sampler,
vec3(N.xy, -N.z),
0.0).rgb;
#endif // LIGHTMAP
radiances.radiance = textureSampleLevel(
bindings::specular_environment_maps[cubemap_index],
bindings::environment_map_sampler,
vec3(R.xy, -R.z),
radiance_level).rgb;
return radiances;
}
#else // MULTIPLE_LIGHT_PROBES_IN_ARRAY
fn compute_radiances(
perceptual_roughness: f32,
N: vec3<f32>,
R: vec3<f32>,
world_position: vec3<f32>,
) -> EnvironmentMapRadiances {
var radiances: EnvironmentMapRadiances;
if (light_probes.view_cubemap_index < 0) {
radiances.irradiance = vec3(0.0);
radiances.radiance = vec3(0.0);
return radiances;
}
// Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
// Technically we could use textureNumLevels(specular_environment_map) - 1 here, but we use a uniform
// because textureNumLevels() does not work on WebGL2
let radiance_level = perceptual_roughness * f32(light_probes.smallest_specular_mip_level_for_view);
#ifndef LIGHTMAP
radiances.irradiance = textureSampleLevel(
bindings::diffuse_environment_map,
bindings::environment_map_sampler,
vec3(N.xy, -N.z),
0.0).rgb;
#endif // LIGHTMAP
radiances.radiance = textureSampleLevel(
bindings::specular_environment_map,
bindings::environment_map_sampler,
vec3(R.xy, -R.z),
radiance_level).rgb;
return radiances;
}
#endif // MULTIPLE_LIGHT_PROBES_IN_ARRAY
fn environment_map_light(
perceptual_roughness: f32,
roughness: f32,
diffuse_color: vec3<f32>,
NdotV: f32,
f_ab: vec2<f32>,
N: vec3<f32>,
R: vec3<f32>,
F0: vec3<f32>,
world_position: vec3<f32>,
) -> EnvironmentMapLight {
let radiances = compute_radiances(perceptual_roughness, N, R, world_position);
// No real world material has specular values under 0.02, so we use this range as a
// "pre-baked specular occlusion" that extinguishes the fresnel term, for artistic control.
// See: https://google.github.io/filament/Filament.html#specularocclusion
let specular_occlusion = saturate(dot(F0, vec3(50.0 * 0.33)));
// Multiscattering approximation: https://www.jcgt.org/published/0008/01/03/paper.pdf
// Useful reference: https://bruop.github.io/ibl
let Fr = max(vec3(1.0 - roughness), F0) - F0;
let kS = F0 + Fr * pow(1.0 - NdotV, 5.0);
let Ess = f_ab.x + f_ab.y;
let FssEss = kS * Ess * specular_occlusion;
let Ems = 1.0 - Ess;
let Favg = F0 + (1.0 - F0) / 21.0;
let Fms = FssEss * Favg / (1.0 - Ems * Favg);
let FmsEms = Fms * Ems;
let Edss = 1.0 - (FssEss + FmsEms);
let kD = diffuse_color * Edss;
var out: EnvironmentMapLight;
// If there's a lightmap, ignore the diffuse component of the reflection
// probe, so we don't double-count light.
#ifdef LIGHTMAP
out.diffuse = vec3(0.0);
#else
out.diffuse = (FmsEms + kD) * radiances.irradiance;
#endif
out.specular = FssEss * radiances.radiance;
return out;
}

View file

@ -1,405 +0,0 @@
//! Light probes for baked global illumination.
use bevy_app::{App, Plugin};
use bevy_asset::load_internal_asset;
use bevy_core_pipeline::core_3d::Camera3d;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
entity::Entity,
query::With,
reflect::ReflectComponent,
schedule::IntoSystemConfigs,
system::{Commands, Local, Query, Res, ResMut, Resource},
};
use bevy_math::{Affine3A, Mat4, Vec3A, Vec4};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
extract_instances::ExtractInstancesPlugin,
primitives::{Aabb, Frustum},
render_asset::RenderAssets,
render_resource::{DynamicUniformBuffer, Shader, ShaderType},
renderer::{RenderDevice, RenderQueue},
texture::Image,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::prelude::GlobalTransform;
use bevy_utils::{EntityHashMap, FloatOrd};
use crate::light_probe::environment_map::{
EnvironmentMapIds, EnvironmentMapLight, RenderViewEnvironmentMaps,
ENVIRONMENT_MAP_SHADER_HANDLE,
};
pub mod environment_map;
/// The maximum number of reflection probes that each view will consider.
///
/// Because the fragment shader does a linear search through the list for each
/// fragment, this number needs to be relatively small.
pub const MAX_VIEW_REFLECTION_PROBES: usize = 8;
/// Adds support for light probes: cuboid bounding regions that apply global
/// illumination to objects within them.
///
/// This also adds support for view environment maps: diffuse and specular
/// cubemaps applied to all objects that a view renders.
pub struct LightProbePlugin;
/// A marker component for a light probe, which is a cuboid region that provides
/// global illumination to all fragments inside it.
///
/// The light probe range is conceptually a unit cube (1×1×1) centered on the
/// origin. The [`bevy_transform::prelude::Transform`] applied to this entity
/// can scale, rotate, or translate that cube so that it contains all fragments
/// that should take this light probe into account.
///
/// Note that a light probe will have no effect unless the entity contains some
/// kind of illumination. At present, the only supported type of illumination is
/// the [`EnvironmentMapLight`].
#[derive(Component, Debug, Clone, Copy, Default, Reflect)]
#[reflect(Component, Default)]
pub struct LightProbe;
/// A GPU type that stores information about a reflection probe.
#[derive(Clone, Copy, ShaderType, Default)]
struct RenderReflectionProbe {
/// The transform from the world space to the model space. This is used to
/// efficiently check for bounding box intersection.
inverse_transpose_transform: [Vec4; 3],
/// The index of the environment map in the diffuse and specular cubemap
/// binding arrays.
cubemap_index: i32,
}
/// A per-view shader uniform that specifies all the light probes that the view
/// takes into account.
#[derive(ShaderType)]
pub struct LightProbesUniform {
/// The list of applicable reflection probes, sorted from nearest to the
/// camera to the farthest away from the camera.
reflection_probes: [RenderReflectionProbe; MAX_VIEW_REFLECTION_PROBES],
/// The number of reflection probes in the list.
reflection_probe_count: i32,
/// The index of the diffuse and specular environment maps associated with
/// the view itself. This is used as a fallback if no reflection probe in
/// the list contains the fragment.
view_cubemap_index: i32,
/// The smallest valid mipmap level for the specular environment cubemap
/// associated with the view.
smallest_specular_mip_level_for_view: u32,
}
/// A map from each camera to the light probe uniform associated with it.
#[derive(Resource, Default, Deref, DerefMut)]
struct RenderLightProbes(EntityHashMap<Entity, LightProbesUniform>);
/// A GPU buffer that stores information about all light probes.
#[derive(Resource, Default, Deref, DerefMut)]
pub struct LightProbesBuffer(DynamicUniformBuffer<LightProbesUniform>);
/// A component attached to each camera in the render world that stores the
/// index of the [`LightProbesUniform`] in the [`LightProbesBuffer`].
#[derive(Component, Default, Deref, DerefMut)]
pub struct ViewLightProbesUniformOffset(u32);
/// Information that [`gather_light_probes`] keeps about each light probe.
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct LightProbeInfo {
// The transform from world space to light probe space.
inverse_transform: Mat4,
// The transform from light probe space to world space.
affine_transform: Affine3A,
// The diffuse and specular environment maps associated with this light
// probe.
environment_maps: EnvironmentMapIds,
}
impl LightProbe {
/// Creates a new light probe component.
#[inline]
pub fn new() -> Self {
Self
}
}
impl Plugin for LightProbePlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
ENVIRONMENT_MAP_SHADER_HANDLE,
"environment_map.wgsl",
Shader::from_wgsl
);
app.register_type::<LightProbe>()
.register_type::<EnvironmentMapLight>();
}
fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new())
.init_resource::<LightProbesBuffer>()
.init_resource::<RenderLightProbes>()
.add_systems(ExtractSchedule, gather_light_probes)
.add_systems(
Render,
upload_light_probes.in_set(RenderSet::PrepareResources),
);
}
}
/// Gathers up all light probes in the scene and assigns them to views,
/// performing frustum culling and distance sorting in the process.
///
/// This populates the [`RenderLightProbes`] resource.
fn gather_light_probes(
mut render_light_probes: ResMut<RenderLightProbes>,
image_assets: Res<RenderAssets<Image>>,
light_probe_query: Extract<Query<(&GlobalTransform, &EnvironmentMapLight), With<LightProbe>>>,
view_query: Extract<
Query<
(
Entity,
&GlobalTransform,
&Frustum,
Option<&EnvironmentMapLight>,
),
With<Camera3d>,
>,
>,
mut light_probes: Local<Vec<LightProbeInfo>>,
mut view_light_probes: Local<Vec<LightProbeInfo>>,
mut commands: Commands,
) {
// Create [`LightProbeInfo`] for every light probe in the scene.
light_probes.clear();
light_probes.extend(
light_probe_query
.iter()
.filter_map(|query_row| LightProbeInfo::new(query_row, &image_assets)),
);
// Build up the light probes uniform and the key table.
render_light_probes.clear();
for (view_entity, view_transform, view_frustum, view_environment_maps) in view_query.iter() {
// Cull light probes outside the view frustum.
view_light_probes.clear();
view_light_probes.extend(
light_probes
.iter()
.filter(|light_probe_info| light_probe_info.frustum_cull(view_frustum))
.cloned(),
);
// Sort by distance to camera.
view_light_probes.sort_by_cached_key(|light_probe_info| {
light_probe_info.camera_distance_sort_key(view_transform)
});
// Create the light probes uniform.
let (light_probes_uniform, render_view_environment_maps) =
LightProbesUniform::build(view_environment_maps, &view_light_probes, &image_assets);
// Record the uniforms.
render_light_probes.insert(view_entity, light_probes_uniform);
// Record the per-view environment maps.
let mut commands = commands.get_or_spawn(view_entity);
if render_view_environment_maps.is_empty() {
commands.remove::<RenderViewEnvironmentMaps>();
} else {
commands.insert(render_view_environment_maps);
}
}
}
/// Uploads the result of [`gather_light_probes`] to the GPU.
fn upload_light_probes(
mut commands: Commands,
light_probes_uniforms: Res<RenderLightProbes>,
mut light_probes_buffer: ResMut<LightProbesBuffer>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
// Get the uniform buffer writer.
let Some(mut writer) =
light_probes_buffer.get_writer(light_probes_uniforms.len(), &render_device, &render_queue)
else {
return;
};
// Send each view's uniforms to the GPU.
for (&view_entity, light_probes_uniform) in light_probes_uniforms.iter() {
commands
.entity(view_entity)
.insert(ViewLightProbesUniformOffset(
writer.write(light_probes_uniform),
));
}
}
impl Default for LightProbesUniform {
fn default() -> Self {
Self {
reflection_probes: [RenderReflectionProbe::default(); MAX_VIEW_REFLECTION_PROBES],
reflection_probe_count: 0,
view_cubemap_index: -1,
smallest_specular_mip_level_for_view: 0,
}
}
}
impl LightProbesUniform {
/// Constructs a [`LightProbesUniform`] containing all the environment maps
/// that fragments rendered by a single view need to consider.
///
/// The `view_environment_maps` parameter describes the environment maps
/// attached to the view. The `light_probes` parameter is expected to be the
/// list of light probes in the scene, sorted by increasing view distance
/// from the camera.
fn build(
view_environment_maps: Option<&EnvironmentMapLight>,
light_probes: &[LightProbeInfo],
image_assets: &RenderAssets<Image>,
) -> (LightProbesUniform, RenderViewEnvironmentMaps) {
let mut render_view_environment_maps = RenderViewEnvironmentMaps::new();
// Find the index of the cubemap associated with the view, and determine
// its smallest mip level.
let (mut view_cubemap_index, mut smallest_specular_mip_level_for_view) = (-1, 0);
if let Some(EnvironmentMapLight {
diffuse_map: diffuse_map_handle,
specular_map: specular_map_handle,
}) = view_environment_maps
{
if let (Some(_), Some(specular_map)) = (
image_assets.get(diffuse_map_handle),
image_assets.get(specular_map_handle),
) {
view_cubemap_index =
render_view_environment_maps.get_or_insert_cubemap(&EnvironmentMapIds {
diffuse: diffuse_map_handle.id(),
specular: specular_map_handle.id(),
}) as i32;
smallest_specular_mip_level_for_view = specular_map.mip_level_count - 1;
}
};
// Initialize the uniform to only contain the view environment map, if
// applicable.
let mut uniform = LightProbesUniform {
reflection_probes: [RenderReflectionProbe::default(); MAX_VIEW_REFLECTION_PROBES],
reflection_probe_count: light_probes.len().min(MAX_VIEW_REFLECTION_PROBES) as i32,
view_cubemap_index,
smallest_specular_mip_level_for_view,
};
// Add reflection probes from the scene, if supported by the current
// platform.
uniform.maybe_gather_reflection_probes(&mut render_view_environment_maps, light_probes);
(uniform, render_view_environment_maps)
}
/// Gathers up all reflection probes in the scene and writes them into this
/// uniform and `render_view_environment_maps`.
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
fn maybe_gather_reflection_probes(
&mut self,
render_view_environment_maps: &mut RenderViewEnvironmentMaps,
light_probes: &[LightProbeInfo],
) {
for (reflection_probe, light_probe) in self
.reflection_probes
.iter_mut()
.zip(light_probes.iter().take(MAX_VIEW_REFLECTION_PROBES))
{
// Determine the index of the cubemap in the binding array.
let cubemap_index = render_view_environment_maps
.get_or_insert_cubemap(&light_probe.environment_maps)
as i32;
// Transpose the inverse transform to compress the structure on the
// GPU (from 4 `Vec4`s to 3 `Vec4`s). The shader will transpose it
// to recover the original inverse transform.
let inverse_transpose_transform = light_probe.inverse_transform.transpose();
// Write in the reflection probe data.
*reflection_probe = RenderReflectionProbe {
inverse_transpose_transform: [
inverse_transpose_transform.x_axis,
inverse_transpose_transform.y_axis,
inverse_transpose_transform.z_axis,
],
cubemap_index,
};
}
}
/// This is the version of `maybe_gather_reflection_probes` used on
/// platforms in which binding arrays aren't available. It's simply a no-op.
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
fn maybe_gather_reflection_probes(
&mut self,
_: &mut RenderViewEnvironmentMaps,
_: &[LightProbeInfo],
) {
}
}
impl LightProbeInfo {
/// Given the set of light probe components, constructs and returns
/// [`LightProbeInfo`]. This is done for every light probe in the scene
/// every frame.
fn new(
(light_probe_transform, environment_map): (&GlobalTransform, &EnvironmentMapLight),
image_assets: &RenderAssets<Image>,
) -> Option<LightProbeInfo> {
if image_assets.get(&environment_map.diffuse_map).is_none()
|| image_assets.get(&environment_map.specular_map).is_none()
{
return None;
}
Some(LightProbeInfo {
affine_transform: light_probe_transform.affine(),
inverse_transform: light_probe_transform.compute_matrix().inverse(),
environment_maps: EnvironmentMapIds {
diffuse: environment_map.diffuse_map.id(),
specular: environment_map.specular_map.id(),
},
})
}
/// Returns true if this light probe is in the viewing frustum of the camera
/// or false if it isn't.
fn frustum_cull(&self, view_frustum: &Frustum) -> bool {
view_frustum.intersects_obb(
&Aabb {
center: Vec3A::default(),
half_extents: Vec3A::splat(0.5),
},
&self.affine_transform,
true,
false,
)
}
/// Returns the squared distance from this light probe to the camera,
/// suitable for distance sorting.
fn camera_distance_sort_key(&self, view_transform: &GlobalTransform) -> FloatOrd {
FloatOrd(
(self.affine_transform.translation - view_transform.translation_vec3a())
.length_squared(),
)
}
}

View file

@ -1,4 +1,4 @@
use crate::{environment_map::RenderViewEnvironmentMaps, *};
use crate::*;
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle};
use bevy_core_pipeline::{
@ -466,12 +466,14 @@ pub fn queue_material_meshes<M: Material>(
render_materials: Res<RenderMaterials<M>>,
mut render_mesh_instances: ResMut<RenderMeshInstances>,
render_material_instances: Res<RenderMaterialInstances<M>>,
images: Res<RenderAssets<Image>>,
render_lightmaps: Res<RenderLightmaps>,
mut views: Query<(
&ExtractedView,
&VisibleEntities,
Option<&Tonemapping>,
Option<&DebandDither>,
Option<&EnvironmentMapLight>,
Option<&ShadowFilteringMethod>,
Has<ScreenSpaceAmbientOcclusionSettings>,
(
@ -487,7 +489,6 @@ pub fn queue_material_meshes<M: Material>(
&mut RenderPhase<AlphaMask3d>,
&mut RenderPhase<Transmissive3d>,
&mut RenderPhase<Transparent3d>,
Has<RenderViewEnvironmentMaps>,
)>,
) where
M::Data: PartialEq + Eq + Hash + Clone,
@ -497,6 +498,7 @@ pub fn queue_material_meshes<M: Material>(
visible_entities,
tonemapping,
dither,
environment_map,
shadow_filter_method,
ssao,
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
@ -507,7 +509,6 @@ pub fn queue_material_meshes<M: Material>(
mut alpha_mask_phase,
mut transmissive_phase,
mut transparent_phase,
has_environment_maps,
) in &mut views
{
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
@ -538,7 +539,9 @@ pub fn queue_material_meshes<M: Material>(
view_key |= MeshPipelineKey::TEMPORAL_JITTER;
}
if has_environment_maps {
let environment_map_loaded = environment_map.is_some_and(|map| map.is_loaded(&images));
if environment_map_loaded {
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
}

View file

@ -196,6 +196,7 @@ pub struct GpuLights {
n_directional_lights: u32,
// offset from spot light's light index to spot light's shadow map index
spot_light_shadowmap_offset: i32,
environment_map_smallest_specular_mip_level: u32,
}
// NOTE: this must be kept in sync with the same constants in pbr.frag
@ -643,12 +644,18 @@ pub(crate) fn spot_light_projection_matrix(angle: f32) -> Mat4 {
pub fn prepare_lights(
mut commands: Commands,
mut texture_cache: ResMut<TextureCache>,
images: Res<RenderAssets<Image>>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut global_light_meta: ResMut<GlobalLightMeta>,
mut light_meta: ResMut<LightMeta>,
views: Query<
(Entity, &ExtractedView, &ExtractedClusterConfig),
(
Entity,
&ExtractedView,
&ExtractedClusterConfig,
Option<&EnvironmentMapLight>,
),
With<RenderPhase<Transparent3d>>,
>,
ambient_light: Res<AmbientLight>,
@ -894,7 +901,7 @@ pub fn prepare_lights(
.write_buffer(&render_device, &render_queue);
// set up light data for each view
for (entity, extracted_view, clusters) in &views {
for (entity, extracted_view, clusters, environment_map) in &views {
let point_light_depth_texture = texture_cache.get(
&render_device,
TextureDescriptor {
@ -961,6 +968,10 @@ pub fn prepare_lights(
// index to shadow map index, we need to subtract point light count and add directional shadowmap count.
spot_light_shadowmap_offset: num_directional_cascades_enabled as i32
- point_light_count as i32,
environment_map_smallest_specular_mip_level: environment_map
.and_then(|env_map| images.get(&env_map.specular_map))
.map(|specular_map| specular_map.mip_level_count - 1)
.unwrap_or(0),
};
// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query

View file

@ -1,8 +1,3 @@
use crate::{
MaterialBindGroupId, NotShadowCaster, NotShadowReceiver, PreviousGlobalTransform, Shadow,
ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
};
use bevy_app::{Plugin, PostUpdate};
use bevy_asset::{load_internal_asset, AssetId, Handle};
use bevy_core_pipeline::{
@ -26,9 +21,7 @@ use bevy_render::{
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::{
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
texture::*,
view::{ViewTarget, ViewUniformOffset, ViewVisibility},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
@ -874,9 +867,6 @@ impl SpecializedMeshPipeline for MeshPipeline {
},
));
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
let format = if key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
@ -1005,7 +995,6 @@ pub fn prepare_mesh_bind_group(
let Some(model) = mesh_uniforms.binding() else {
return;
};
groups.model_only = Some(layouts.model_only(&render_device, &model));
let skin = skins_uniform.buffer.buffer();
@ -1043,7 +1032,6 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
Read<ViewUniformOffset>,
Read<ViewLightsUniformOffset>,
Read<ViewFogUniformOffset>,
Read<ViewLightProbesUniformOffset>,
Read<MeshViewBindGroup>,
);
type ItemData = ();
@ -1051,7 +1039,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
#[inline]
fn render<'w>(
_item: &P,
(view_uniform, view_lights, view_fog, view_light_probes, mesh_view_bind_group): ROQueryItem<
(view_uniform, view_lights, view_fog, mesh_view_bind_group): ROQueryItem<
'w,
Self::ViewData,
>,
@ -1062,12 +1050,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
pass.set_bind_group(
I,
&mesh_view_bind_group.value,
&[
view_uniform.offset,
view_lights.offset,
view_fog.offset,
**view_light_probes,
],
&[view_uniform.offset, view_lights.offset, view_fog.offset],
);
RenderCommandResult::Success

View file

@ -17,7 +17,7 @@ use bevy_render::{
render_asset::RenderAssets,
render_resource::{binding_types::*, *},
renderer::RenderDevice,
texture::{BevyDefault, FallbackImage, FallbackImageMsaa, FallbackImageZero, Image},
texture::{BevyDefault, FallbackImageCubemap, FallbackImageMsaa, FallbackImageZero, Image},
view::{Msaa, ViewUniform, ViewUniforms},
};
@ -27,10 +27,9 @@ use bevy_render::render_resource::binding_types::texture_cube;
use bevy_render::render_resource::binding_types::{texture_2d_array, texture_cube_array};
use crate::{
environment_map::{self, RenderViewBindGroupEntries, RenderViewEnvironmentMaps},
prepass, FogMeta, GlobalLightMeta, GpuFog, GpuLights, GpuPointLights, LightMeta,
LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey,
ScreenSpaceAmbientOcclusionTextures, ShadowSamplers, ViewClusterBindings, ViewShadowBindings,
environment_map, prepass, EnvironmentMapLight, FogMeta, GlobalLightMeta, GpuFog, GpuLights,
GpuPointLights, LightMeta, MeshPipeline, MeshPipelineKey, ScreenSpaceAmbientOcclusionTextures,
ShadowSamplers, ViewClusterBindings, ViewShadowBindings,
};
#[derive(Clone)]
@ -235,11 +234,9 @@ fn layout_entries(
(9, uniform_buffer::<GlobalsUniform>(false)),
// Fog
(10, uniform_buffer::<GpuFog>(true)),
// Light probes
(11, uniform_buffer::<LightProbesUniform>(true)),
// Screen space ambient occlusion texture
(
12,
11,
texture_2d(TextureSampleType::Float { filterable: false }),
),
),
@ -248,16 +245,16 @@ fn layout_entries(
// EnvironmentMapLight
let environment_map_entries = environment_map::get_bind_group_layout_entries();
entries = entries.extend_with_indices((
(13, environment_map_entries[0]),
(14, environment_map_entries[1]),
(15, environment_map_entries[2]),
(12, environment_map_entries[0]),
(13, environment_map_entries[1]),
(14, environment_map_entries[2]),
));
// Tonemapping
let tonemapping_lut_entries = get_lut_bind_group_layout_entries();
entries = entries.extend_with_indices((
(16, tonemapping_lut_entries[0]),
(17, tonemapping_lut_entries[1]),
(15, tonemapping_lut_entries[0]),
(16, tonemapping_lut_entries[1]),
));
// Prepass
@ -267,7 +264,7 @@ fn layout_entries(
{
for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key)
.iter()
.zip([18, 19, 20, 21])
.zip([17, 18, 19, 20])
{
if let Some(entry) = entry {
entries = entries.extend_with_indices(((binding as u32, *entry),));
@ -278,10 +275,10 @@ fn layout_entries(
// View Transmission Texture
entries = entries.extend_with_indices((
(
22,
21,
texture_2d(TextureSampleType::Float { filterable: true }),
),
(23, sampler(SamplerBindingType::Filtering)),
(22, sampler(SamplerBindingType::Filtering)),
));
entries.to_vec()
@ -334,19 +331,18 @@ pub fn prepare_mesh_view_bind_groups(
Option<&ScreenSpaceAmbientOcclusionTextures>,
Option<&ViewPrepassTextures>,
Option<&ViewTransmissionTexture>,
Option<&EnvironmentMapLight>,
&Tonemapping,
Option<&RenderViewEnvironmentMaps>,
)>,
(images, mut fallback_images, fallback_image, fallback_image_zero): (
(images, mut fallback_images, fallback_cubemap, fallback_image_zero): (
Res<RenderAssets<Image>>,
FallbackImageMsaa,
Res<FallbackImage>,
Res<FallbackImageCubemap>,
Res<FallbackImageZero>,
),
msaa: Res<Msaa>,
globals_buffer: Res<GlobalsBuffer>,
tonemapping_luts: Res<TonemappingLuts>,
light_probes_buffer: Res<LightProbesBuffer>,
) {
if let (
Some(view_binding),
@ -354,14 +350,12 @@ pub fn prepare_mesh_view_bind_groups(
Some(point_light_binding),
Some(globals),
Some(fog_binding),
Some(light_probes_binding),
) = (
view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.binding(),
global_light_meta.gpu_point_lights.binding(),
globals_buffer.buffer.binding(),
fog_meta.gpu_fogs.binding(),
light_probes_buffer.binding(),
) {
for (
entity,
@ -370,8 +364,8 @@ pub fn prepare_mesh_view_bind_groups(
ssao_textures,
prepass_textures,
transmission_texture,
environment_map,
tonemapping,
render_view_environment_maps,
) in &views
{
let fallback_ssao = fallback_images
@ -399,23 +393,19 @@ pub fn prepare_mesh_view_bind_groups(
(8, cluster_bindings.offsets_and_counts_binding().unwrap()),
(9, globals.clone()),
(10, fog_binding.clone()),
(11, light_probes_binding.clone()),
(12, ssao_view),
(11, ssao_view),
));
let bind_group_entries = RenderViewBindGroupEntries::get(
render_view_environment_maps,
&images,
&fallback_image,
);
let env_map_bindings =
environment_map::get_bindings(environment_map, &images, &fallback_cubemap);
entries = entries.extend_with_indices((
(13, bind_group_entries.diffuse_texture_views()),
(14, bind_group_entries.specular_texture_views()),
(15, bind_group_entries.sampler),
(12, env_map_bindings.0),
(13, env_map_bindings.1),
(14, env_map_bindings.2),
));
let lut_bindings = get_lut_bindings(&images, &tonemapping_luts, tonemapping);
entries = entries.extend_with_indices(((16, lut_bindings.0), (17, lut_bindings.1)));
entries = entries.extend_with_indices(((15, lut_bindings.0), (16, lut_bindings.1)));
// When using WebGL, we can't have a depth texture with multisampling
let prepass_bindings;
@ -425,7 +415,7 @@ pub fn prepare_mesh_view_bind_groups(
for (binding, index) in prepass_bindings
.iter()
.map(Option::as_ref)
.zip([18, 19, 20, 21])
.zip([17, 18, 19, 20])
.flat_map(|(b, i)| b.map(|b| (b, i)))
{
entries = entries.extend_with_indices(((index, binding),));
@ -441,7 +431,7 @@ pub fn prepare_mesh_view_bind_groups(
.unwrap_or(&fallback_image_zero.sampler);
entries =
entries.extend_with_indices(((22, transmission_view), (23, transmission_sampler)));
entries.extend_with_indices(((21, transmission_view), (22, transmission_sampler)));
commands.entity(entity).insert(MeshViewBindGroup {
value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries),

View file

@ -33,50 +33,44 @@
@group(0) @binding(9) var<uniform> globals: Globals;
@group(0) @binding(10) var<uniform> fog: types::Fog;
@group(0) @binding(11) var<uniform> light_probes: types::LightProbes;
@group(0) @binding(12) var screen_space_ambient_occlusion_texture: texture_2d<f32>;
@group(0) @binding(11) var screen_space_ambient_occlusion_texture: texture_2d<f32>;
#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY
@group(0) @binding(13) var diffuse_environment_maps: binding_array<texture_cube<f32>>;
@group(0) @binding(14) var specular_environment_maps: binding_array<texture_cube<f32>>;
#else
@group(0) @binding(13) var diffuse_environment_map: texture_cube<f32>;
@group(0) @binding(14) var specular_environment_map: texture_cube<f32>;
#endif
@group(0) @binding(15) var environment_map_sampler: sampler;
@group(0) @binding(12) var environment_map_diffuse: texture_cube<f32>;
@group(0) @binding(13) var environment_map_specular: texture_cube<f32>;
@group(0) @binding(14) var environment_map_sampler: sampler;
@group(0) @binding(16) var dt_lut_texture: texture_3d<f32>;
@group(0) @binding(17) var dt_lut_sampler: sampler;
@group(0) @binding(15) var dt_lut_texture: texture_3d<f32>;
@group(0) @binding(16) var dt_lut_sampler: sampler;
#ifdef MULTISAMPLED
#ifdef DEPTH_PREPASS
@group(0) @binding(18) var depth_prepass_texture: texture_depth_multisampled_2d;
@group(0) @binding(17) var depth_prepass_texture: texture_depth_multisampled_2d;
#endif // DEPTH_PREPASS
#ifdef NORMAL_PREPASS
@group(0) @binding(19) var normal_prepass_texture: texture_multisampled_2d<f32>;
@group(0) @binding(18) var normal_prepass_texture: texture_multisampled_2d<f32>;
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(20) var motion_vector_prepass_texture: texture_multisampled_2d<f32>;
@group(0) @binding(19) var motion_vector_prepass_texture: texture_multisampled_2d<f32>;
#endif // MOTION_VECTOR_PREPASS
#else // MULTISAMPLED
#ifdef DEPTH_PREPASS
@group(0) @binding(18) var depth_prepass_texture: texture_depth_2d;
@group(0) @binding(17) var depth_prepass_texture: texture_depth_2d;
#endif // DEPTH_PREPASS
#ifdef NORMAL_PREPASS
@group(0) @binding(19) var normal_prepass_texture: texture_2d<f32>;
@group(0) @binding(18) var normal_prepass_texture: texture_2d<f32>;
#endif // NORMAL_PREPASS
#ifdef MOTION_VECTOR_PREPASS
@group(0) @binding(20) var motion_vector_prepass_texture: texture_2d<f32>;
@group(0) @binding(19) var motion_vector_prepass_texture: texture_2d<f32>;
#endif // MOTION_VECTOR_PREPASS
#endif // MULTISAMPLED
#ifdef DEFERRED_PREPASS
@group(0) @binding(21) var deferred_prepass_texture: texture_2d<u32>;
@group(0) @binding(20) var deferred_prepass_texture: texture_2d<u32>;
#endif // DEFERRED_PREPASS
@group(0) @binding(22) var view_transmission_texture: texture_2d<f32>;
@group(0) @binding(23) var view_transmission_sampler: sampler;
@group(0) @binding(21) var view_transmission_texture: texture_2d<f32>;
@group(0) @binding(22) var view_transmission_sampler: sampler;

View file

@ -109,22 +109,3 @@ struct ClusterOffsetsAndCounts {
data: array<vec4<u32>, 1024u>,
};
#endif
struct ReflectionProbe {
// This is stored as the transpose in order to save space in this structure.
// It'll be transposed in the `environment_map_light` function.
inverse_transpose_transform: mat3x4<f32>,
cubemap_index: i32,
};
struct LightProbes {
// This must match `MAX_VIEW_REFLECTION_PROBES` on the Rust side.
reflection_probes: array<ReflectionProbe, 8u>,
reflection_probe_count: i32,
// The index of the view environment map cubemap binding, or -1 if there's
// no such cubemap.
view_cubemap_index: i32,
// The smallest valid mipmap level for the specular environment cubemap
// associated with the view.
smallest_specular_mip_level_for_view: u32,
};

View file

@ -322,16 +322,7 @@ fn apply_pbr_lighting(
// Environment map light (indirect)
#ifdef ENVIRONMENT_MAP
let environment_light = environment_map::environment_map_light(
perceptual_roughness,
roughness,
diffuse_color,
NdotV,
f_ab,
in.N,
R,
F0,
in.world_position.xyz);
let environment_light = environment_map::environment_map_light(perceptual_roughness, roughness, diffuse_color, NdotV, f_ab, in.N, R, F0);
indirect_light += (environment_light.diffuse * occlusion) + environment_light.specular;
// we'll use the specular component of the transmitted environment
@ -357,16 +348,7 @@ fn apply_pbr_lighting(
refract(in.V, -in.N, 1.0 / ior) * thickness // add refracted vector scaled by thickness, towards exit point
); // normalize to find exit point view vector
let transmitted_environment_light = bevy_pbr::environment_map::environment_map_light(
perceptual_roughness,
roughness,
vec3<f32>(1.0),
1.0,
f_ab,
-in.N,
T,
vec3<f32>(1.0),
in.world_position.xyz);
let transmitted_environment_light = bevy_pbr::environment_map::environment_map_light(perceptual_roughness, roughness, vec3<f32>(1.0), 1.0, f_ab, -in.N, T, vec3<f32>(1.0));
transmitted_light += transmitted_environment_light.diffuse * diffuse_transmissive_color;
specular_transmitted_environment_light = transmitted_environment_light.specular * specular_transmissive_color;
}

View file

@ -20,7 +20,7 @@ dds = ["ddsfile"]
pnm = ["image/pnm"]
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out", "naga_oil/glsl"]
shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out"]
shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"]
# For ktx2 supercompression
@ -67,9 +67,7 @@ wgpu = { version = "0.18", features = [
"fragile-send-sync-non-atomic-wasm",
] }
naga = { version = "0.14.2", features = ["wgsl-in"] }
naga_oil = { version = "0.11", default-features = false, features = [
"test_shader",
] }
naga_oil = "0.11"
serde = { version = "1", features = ["derive"] }
bitflags = "2.3"
bytemuck = { version = "1.5", features = ["derive"] }

View file

@ -213,12 +213,7 @@ impl From<&Source> for naga_oil::compose::ShaderLanguage {
fn from(value: &Source) -> Self {
match value {
Source::Wgsl(_) => naga_oil::compose::ShaderLanguage::Wgsl,
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
Source::Glsl(_, _) => naga_oil::compose::ShaderLanguage::Glsl,
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
Source::Glsl(_, _) => panic!(
"GLSL is not supported in this configuration; use the feature `shader_format_glsl`"
),
Source::SpirV(_) => panic!("spirv not yet implemented"),
}
}
@ -228,16 +223,13 @@ impl From<&Source> for naga_oil::compose::ShaderType {
fn from(value: &Source) -> Self {
match value {
Source::Wgsl(_) => naga_oil::compose::ShaderType::Wgsl,
#[cfg(any(feature = "shader_format_glsl", target_arch = "wasm32"))]
Source::Glsl(_, shader_stage) => match shader_stage {
naga::ShaderStage::Vertex => naga_oil::compose::ShaderType::GlslVertex,
naga::ShaderStage::Fragment => naga_oil::compose::ShaderType::GlslFragment,
naga::ShaderStage::Compute => panic!("glsl compute not yet implemented"),
},
#[cfg(all(not(feature = "shader_format_glsl"), not(target_arch = "wasm32")))]
Source::Glsl(_, _) => panic!(
"GLSL is not supported in this configuration; use the feature `shader_format_glsl`"
),
Source::Glsl(_, naga::ShaderStage::Vertex) => naga_oil::compose::ShaderType::GlslVertex,
Source::Glsl(_, naga::ShaderStage::Fragment) => {
naga_oil::compose::ShaderType::GlslFragment
}
Source::Glsl(_, naga::ShaderStage::Compute) => {
panic!("glsl compute not yet implemented")
}
Source::SpirV(_) => panic!("spirv not yet implemented"),
}
}

View file

@ -1,345 +0,0 @@
//! This example shows how to place reflection probes in the scene.
//!
//! Press Space to switch between no reflections, environment map reflections
//! (i.e. the skybox only, not the cubes), and a full reflection probe that
//! reflects the skybox and the cubes. Press Enter to pause rotation.
//!
//! Reflection probes don't work on WebGL 2 or WebGPU.
use bevy::core_pipeline::Skybox;
use bevy::prelude::*;
use std::fmt::{Display, Formatter, Result as FmtResult};
// Rotation speed in radians per frame.
const ROTATION_SPEED: f32 = 0.005;
static STOP_ROTATION_HELP_TEXT: &str = "Press Enter to stop rotation";
static START_ROTATION_HELP_TEXT: &str = "Press Enter to start rotation";
static REFLECTION_MODE_HELP_TEXT: &str = "Press Space to switch reflection mode";
// The mode the application is in.
#[derive(Resource)]
struct AppStatus {
// Which environment maps the user has requested to display.
reflection_mode: ReflectionMode,
// Whether the user has requested the scene to rotate.
rotating: bool,
}
// Which environment maps the user has requested to display.
#[derive(Clone, Copy)]
enum ReflectionMode {
// No environment maps are shown.
None = 0,
// Only a world environment map is shown.
EnvironmentMap = 1,
// Both a world environment map and a reflection probe are present. The
// reflection probe is shown in the sphere.
ReflectionProbe = 2,
}
// The various reflection maps.
#[derive(Resource)]
struct Cubemaps {
// The blurry diffuse cubemap. This is used for both the world environment
// map and the reflection probe. (In reality you wouldn't do this, but this
// reduces complexity of this example a bit.)
diffuse: Handle<Image>,
// The specular cubemap that reflects the world, but not the cubes.
specular_environment_map: Handle<Image>,
// The specular cubemap that reflects both the world and the cubes.
specular_reflection_probe: Handle<Image>,
// The skybox cubemap image. This is almost the same as
// `specular_environment_map`.
skybox: Handle<Image>,
}
fn main() {
// Create the app.
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<AppStatus>()
.init_resource::<Cubemaps>()
.add_systems(Startup, setup)
.add_systems(PreUpdate, add_environment_map_to_camera)
.add_systems(Update, change_reflection_type)
.add_systems(Update, toggle_rotation)
.add_systems(
Update,
rotate_camera
.after(toggle_rotation)
.after(change_reflection_type),
)
.add_systems(Update, update_text.after(rotate_camera))
.run();
}
// Spawns all the scene objects.
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
asset_server: Res<AssetServer>,
app_status: Res<AppStatus>,
cubemaps: Res<Cubemaps>,
) {
spawn_scene(&mut commands, &asset_server);
spawn_sphere(&mut commands, &mut meshes, &mut materials);
spawn_reflection_probe(&mut commands, &cubemaps);
spawn_text(&mut commands, &asset_server, &app_status);
}
// Spawns the cubes, light, and camera.
fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(SceneBundle {
scene: asset_server.load("models/cubes/Cubes.glb#Scene0"),
..SceneBundle::default()
});
}
// Creates the sphere mesh and spawns it.
fn spawn_sphere(
commands: &mut Commands,
meshes: &mut Assets<Mesh>,
materials: &mut Assets<StandardMaterial>,
) {
// Create a sphere mesh.
let sphere_mesh = meshes.add(
Mesh::try_from(shape::Icosphere {
radius: 1.0,
subdivisions: 7,
})
.unwrap(),
);
// Create a sphere.
commands.spawn(PbrBundle {
mesh: sphere_mesh.clone(),
material: materials.add(StandardMaterial {
base_color: Color::hex("#ffd891").unwrap(),
metallic: 1.0,
perceptual_roughness: 0.0,
..StandardMaterial::default()
}),
transform: Transform::default(),
..PbrBundle::default()
});
}
// Spawns the reflection probe.
fn spawn_reflection_probe(commands: &mut Commands, cubemaps: &Cubemaps) {
commands.spawn(ReflectionProbeBundle {
spatial: SpatialBundle {
// 2.0 because the sphere's radius is 1.0 and we want to fully enclose it.
transform: Transform::from_scale(Vec3::splat(2.0)),
..SpatialBundle::default()
},
light_probe: LightProbe,
environment_map: EnvironmentMapLight {
diffuse_map: cubemaps.diffuse.clone(),
specular_map: cubemaps.specular_reflection_probe.clone(),
},
});
}
// Spawns the help text.
fn spawn_text(commands: &mut Commands, asset_server: &AssetServer, app_status: &AppStatus) {
// Create the text.
commands.spawn(
TextBundle {
text: app_status.create_text(asset_server),
..TextBundle::default()
}
.with_style(Style {
position_type: PositionType::Absolute,
bottom: Val::Px(10.0),
left: Val::Px(10.0),
..default()
}),
);
}
// Adds a world environment map to the camera. This separate system is needed because the camera is
// managed by the scene spawner, as it's part of the glTF file with the cubes, so we have to add
// the environment map after the fact.
fn add_environment_map_to_camera(
mut commands: Commands,
query: Query<Entity, Added<Camera3d>>,
cubemaps: Res<Cubemaps>,
) {
for camera_entity in query.iter() {
commands
.entity(camera_entity)
.insert(create_camera_environment_map_light(&cubemaps))
.insert(Skybox(cubemaps.skybox.clone()));
}
}
// A system that handles switching between different reflection modes.
fn change_reflection_type(
mut commands: Commands,
light_probe_query: Query<Entity, With<LightProbe>>,
camera_query: Query<Entity, With<Camera3d>>,
keyboard: Res<ButtonInput<KeyCode>>,
mut app_status: ResMut<AppStatus>,
cubemaps: Res<Cubemaps>,
) {
// Only do anything if space was pressed.
if !keyboard.just_pressed(KeyCode::Space) {
return;
}
// Switch reflection mode.
app_status.reflection_mode =
ReflectionMode::try_from((app_status.reflection_mode as u32 + 1) % 3).unwrap();
// Add or remove the light probe.
for light_probe in light_probe_query.iter() {
commands.entity(light_probe).despawn();
}
match app_status.reflection_mode {
ReflectionMode::None | ReflectionMode::EnvironmentMap => {}
ReflectionMode::ReflectionProbe => spawn_reflection_probe(&mut commands, &cubemaps),
}
// Add or remove the environment map from the camera.
for camera in camera_query.iter() {
match app_status.reflection_mode {
ReflectionMode::None => {
commands.entity(camera).remove::<EnvironmentMapLight>();
}
ReflectionMode::EnvironmentMap | ReflectionMode::ReflectionProbe => {
commands
.entity(camera)
.insert(create_camera_environment_map_light(&cubemaps));
}
}
}
}
// A system that handles enabling and disabling rotation.
fn toggle_rotation(keyboard: Res<ButtonInput<KeyCode>>, mut app_status: ResMut<AppStatus>) {
if keyboard.just_pressed(KeyCode::Enter) {
app_status.rotating = !app_status.rotating;
}
}
// A system that updates the help text.
fn update_text(
mut text_query: Query<&mut Text>,
app_status: Res<AppStatus>,
asset_server: Res<AssetServer>,
) {
for mut text in text_query.iter_mut() {
*text = app_status.create_text(&asset_server);
}
}
impl TryFrom<u32> for ReflectionMode {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(ReflectionMode::None),
1 => Ok(ReflectionMode::EnvironmentMap),
2 => Ok(ReflectionMode::ReflectionProbe),
_ => Err(()),
}
}
}
impl Display for ReflectionMode {
fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
let text = match *self {
ReflectionMode::None => "No reflections",
ReflectionMode::EnvironmentMap => "Environment map",
ReflectionMode::ReflectionProbe => "Reflection probe",
};
formatter.write_str(text)
}
}
impl AppStatus {
// Constructs the help text at the bottom of the screen based on the
// application status.
fn create_text(&self, asset_server: &AssetServer) -> Text {
let rotation_help_text = if self.rotating {
STOP_ROTATION_HELP_TEXT
} else {
START_ROTATION_HELP_TEXT
};
Text::from_section(
format!(
"{}\n{}\n{}",
self.reflection_mode, rotation_help_text, REFLECTION_MODE_HELP_TEXT
),
TextStyle {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 24.0,
color: Color::ANTIQUE_WHITE,
},
)
}
}
// Creates the world environment map light, used as a fallback if no reflection
// probe is applicable to a mesh.
fn create_camera_environment_map_light(cubemaps: &Cubemaps) -> EnvironmentMapLight {
EnvironmentMapLight {
diffuse_map: cubemaps.diffuse.clone(),
specular_map: cubemaps.specular_environment_map.clone(),
}
}
// Rotates the camera a bit every frame.
fn rotate_camera(
mut camera_query: Query<&mut Transform, With<Camera3d>>,
app_status: Res<AppStatus>,
) {
if !app_status.rotating {
return;
}
for mut transform in camera_query.iter_mut() {
transform.translation = Vec2::from_angle(ROTATION_SPEED)
.rotate(transform.translation.xz())
.extend(transform.translation.y)
.xzy();
transform.look_at(Vec3::ZERO, Vec3::Y);
}
}
// Loads the cubemaps from the assets directory.
impl FromWorld for Cubemaps {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
// Just use the specular map for the skybox since it's not too blurry.
// In reality you wouldn't do this--you'd use a real skybox texture--but
// reusing the textures like this saves space in the Bevy repository.
let specular_map = asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2");
Cubemaps {
diffuse: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_reflection_probe: asset_server
.load("environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2"),
specular_environment_map: specular_map.clone(),
skybox: specular_map,
}
}
}
impl Default for AppStatus {
fn default() -> Self {
Self {
reflection_mode: ReflectionMode::ReflectionProbe,
rotating: true,
}
}
}

View file

@ -134,7 +134,6 @@ Example | Description
[Parallax Mapping](../examples/3d/parallax_mapping.rs) | Demonstrates use of a normal map and depth map for parallax mapping
[Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
[Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
[Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes
[Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
[Screen Space Ambient Occlusion](../examples/3d/ssao.rs) | A scene showcasing screen space ambient occlusion
[Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene