mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
# 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:
parent
64a15f1b10
commit
3d996639a0
24 changed files with 249 additions and 1523 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -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.
Binary file not shown.
|
@ -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> {
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
50
crates/bevy_pbr/src/environment_map/environment_map.wgsl
Normal file
50
crates/bevy_pbr/src/environment_map/environment_map.wgsl
Normal 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;
|
||||
}
|
91
crates/bevy_pbr/src/environment_map/mod.rs
Normal file
91
crates/bevy_pbr/src/environment_map/mod.rs
Normal 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),
|
||||
]
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue