diff --git a/Cargo.toml b/Cargo.toml index ab75415142..fe49a885a6 100644 --- a/Cargo.toml +++ b/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 diff --git a/assets/environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2 b/assets/environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2 deleted file mode 100644 index 9c2f2a85a3..0000000000 Binary files a/assets/environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2 and /dev/null differ diff --git a/assets/models/cubes/Cubes.glb b/assets/models/cubes/Cubes.glb deleted file mode 100644 index 0908008267..0000000000 Binary files a/assets/models/cubes/Cubes.glb and /dev/null differ diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index 2426f1dce2..494d86900d 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -7,8 +7,8 @@ @group(0) @binding(3) var dt_lut_texture: texture_3d; @group(0) @binding(4) var dt_lut_sampler: sampler; #else - @group(0) @binding(16) var dt_lut_texture: texture_3d; - @group(0) @binding(17) var dt_lut_sampler: sampler; + @group(0) @binding(15) var dt_lut_texture: texture_3d; + @group(0) @binding(16) var dt_lut_sampler: sampler; #endif fn sample_current_lut(p: vec3) -> vec3 { diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 7e092e5a38..709d78179e 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -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 = [ diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 23eeb71e5b..9d334390e0 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -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 diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index cd9f45ba12..a47a9da519 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -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, ( @@ -408,20 +411,20 @@ pub fn prepare_deferred_lighting_pipelines( Has, Has, ), - Has, ), With, >, + images: Res>, ) { 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; } diff --git a/crates/bevy_pbr/src/environment_map/environment_map.wgsl b/crates/bevy_pbr/src/environment_map/environment_map.wgsl new file mode 100644 index 0000000000..0b05e352c2 --- /dev/null +++ b/crates/bevy_pbr/src/environment_map/environment_map.wgsl @@ -0,0 +1,50 @@ +#define_import_path bevy_pbr::environment_map + +#import bevy_pbr::mesh_view_bindings as bindings; + +struct EnvironmentMapLight { + diffuse: vec3, + specular: vec3, +}; + +fn environment_map_light( + perceptual_roughness: f32, + roughness: f32, + diffuse_color: vec3, + NdotV: f32, + f_ab: vec2, + N: vec3, + R: vec3, + F0: vec3, +) -> 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; +} diff --git a/crates/bevy_pbr/src/environment_map/mod.rs b/crates/bevy_pbr/src/environment_map/mod.rs new file mode 100644 index 0000000000..823f264a17 --- /dev/null +++ b/crates/bevy_pbr/src/environment_map/mod.rs @@ -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 = + 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::() + .add_plugins(ExtractComponentPlugin::::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)] +pub struct EnvironmentMapLight { + pub diffuse_map: Handle, + pub specular_map: Handle, +} + +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) -> 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, + 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), + ] +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 97fd0a0ecc..abb225c99f 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -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::::default(), FogPlugin, ExtractResourcePlugin::::default(), ExtractComponentPlugin::::default(), LightmapPlugin, - LightProbePlugin, )) .configure_sets( PostUpdate, diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs deleted file mode 100644 index ce5ffd8bbb..0000000000 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ /dev/null @@ -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 = - 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, - /// The typically-sharper, mipmapped image that represents specular radiance - /// surrounding a region. - pub specular_map: Handle, -} - -/// 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, - /// The typically-sharper, mipmapped image that represents specular radiance - /// surrounding a region. - pub(crate) specular: AssetId, -} - -/// 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, - /// 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, -} - -/// 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, -} - -/// 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 ::Target>, - - /// As above, but for specular cubemaps. - specular_texture_views: Vec<&'a ::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; - - type Filter = (); - - fn extract(item: QueryItem<'_, Self::Data>) -> Option { - 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, - 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, - 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 ::Target>, - sampler: &mut Option<&'a Sampler>, - image_id: AssetId, - images: &'a RenderAssets, - 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 ::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 ::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 - } -} diff --git a/crates/bevy_pbr/src/light_probe/environment_map.wgsl b/crates/bevy_pbr/src/light_probe/environment_map.wgsl deleted file mode 100644 index a500fe3e73..0000000000 --- a/crates/bevy_pbr/src/light_probe/environment_map.wgsl +++ /dev/null @@ -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, - specular: vec3, -}; - -struct EnvironmentMapRadiances { - irradiance: vec3, - radiance: vec3, -} - -// 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, - R: vec3, - world_position: vec3, -) -> 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( - reflection_probe.inverse_transpose_transform[0], - reflection_probe.inverse_transpose_transform[1], - reflection_probe.inverse_transpose_transform[2], - vec4(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(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, - R: vec3, - world_position: vec3, -) -> 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, - NdotV: f32, - f_ab: vec2, - N: vec3, - R: vec3, - F0: vec3, - world_position: vec3, -) -> 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; -} diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs deleted file mode 100644 index 03ac1af5ea..0000000000 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ /dev/null @@ -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); - -/// A GPU buffer that stores information about all light probes. -#[derive(Resource, Default, Deref, DerefMut)] -pub struct LightProbesBuffer(DynamicUniformBuffer); - -/// 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::() - .register_type::(); - } - - fn finish(&self, app: &mut App) { - let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - render_app - .add_plugins(ExtractInstancesPlugin::::new()) - .init_resource::() - .init_resource::() - .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, - image_assets: Res>, - light_probe_query: Extract>>, - view_query: Extract< - Query< - ( - Entity, - &GlobalTransform, - &Frustum, - Option<&EnvironmentMapLight>, - ), - With, - >, - >, - mut light_probes: Local>, - mut view_light_probes: Local>, - 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::(); - } 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, - mut light_probes_buffer: ResMut, - render_device: Res, - render_queue: Res, -) { - // 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, - ) -> (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, - ) -> Option { - 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(), - ) - } -} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 3af43aeda6..fa2ca38976 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -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( render_materials: Res>, mut render_mesh_instances: ResMut, render_material_instances: Res>, + images: Res>, render_lightmaps: Res, mut views: Query<( &ExtractedView, &VisibleEntities, Option<&Tonemapping>, Option<&DebandDither>, + Option<&EnvironmentMapLight>, Option<&ShadowFilteringMethod>, Has, ( @@ -487,7 +489,6 @@ pub fn queue_material_meshes( &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, - Has, )>, ) where M::Data: PartialEq + Eq + Hash + Clone, @@ -497,6 +498,7 @@ pub fn queue_material_meshes( 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( 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::>(); @@ -538,7 +539,9 @@ pub fn queue_material_meshes( 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; } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 80c7e14113..f7666f1205 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -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, + images: Res>, render_device: Res, render_queue: Res, mut global_light_meta: ResMut, mut light_meta: ResMut, views: Query< - (Entity, &ExtractedView, &ExtractedClusterConfig), + ( + Entity, + &ExtractedView, + &ExtractedClusterConfig, + Option<&EnvironmentMapLight>, + ), With>, >, ambient_light: Res, @@ -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 diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 03bec26da9..4db985e4f8 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -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 RenderCommand

for SetMeshViewBindGroup Read, Read, Read, - Read, Read, ); type ItemData = (); @@ -1051,7 +1039,7 @@ impl RenderCommand

for SetMeshViewBindGroup #[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 RenderCommand

for SetMeshViewBindGroup 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 diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 8b77e81eb3..eb020cf4e5 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -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::(false)), // Fog (10, uniform_buffer::(true)), - // Light probes - (11, uniform_buffer::(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>, FallbackImageMsaa, - Res, + Res, Res, ), msaa: Res, globals_buffer: Res, tonemapping_luts: Res, - light_probes_buffer: Res, ) { 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), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 37d54bf60d..6f4293d6d6 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -33,50 +33,44 @@ @group(0) @binding(9) var globals: Globals; @group(0) @binding(10) var fog: types::Fog; -@group(0) @binding(11) var light_probes: types::LightProbes; -@group(0) @binding(12) var screen_space_ambient_occlusion_texture: texture_2d; +@group(0) @binding(11) var screen_space_ambient_occlusion_texture: texture_2d; -#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY -@group(0) @binding(13) var diffuse_environment_maps: binding_array>; -@group(0) @binding(14) var specular_environment_maps: binding_array>; -#else -@group(0) @binding(13) var diffuse_environment_map: texture_cube; -@group(0) @binding(14) var specular_environment_map: texture_cube; -#endif -@group(0) @binding(15) var environment_map_sampler: sampler; +@group(0) @binding(12) var environment_map_diffuse: texture_cube; +@group(0) @binding(13) var environment_map_specular: texture_cube; +@group(0) @binding(14) var environment_map_sampler: sampler; -@group(0) @binding(16) var dt_lut_texture: texture_3d; -@group(0) @binding(17) var dt_lut_sampler: sampler; +@group(0) @binding(15) var dt_lut_texture: texture_3d; +@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; +@group(0) @binding(18) var normal_prepass_texture: texture_multisampled_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(20) var motion_vector_prepass_texture: texture_multisampled_2d; +@group(0) @binding(19) var motion_vector_prepass_texture: texture_multisampled_2d; #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; +@group(0) @binding(18) var normal_prepass_texture: texture_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(20) var motion_vector_prepass_texture: texture_2d; +@group(0) @binding(19) var motion_vector_prepass_texture: texture_2d; #endif // MOTION_VECTOR_PREPASS #endif // MULTISAMPLED #ifdef DEFERRED_PREPASS -@group(0) @binding(21) var deferred_prepass_texture: texture_2d; +@group(0) @binding(20) var deferred_prepass_texture: texture_2d; #endif // DEFERRED_PREPASS -@group(0) @binding(22) var view_transmission_texture: texture_2d; -@group(0) @binding(23) var view_transmission_sampler: sampler; +@group(0) @binding(21) var view_transmission_texture: texture_2d; +@group(0) @binding(22) var view_transmission_sampler: sampler; diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index 8d643fd719..3062ad671a 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -109,22 +109,3 @@ struct ClusterOffsetsAndCounts { data: array, 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, - cubemap_index: i32, -}; - -struct LightProbes { - // This must match `MAX_VIEW_REFLECTION_PROBES` on the Rust side. - reflection_probes: array, - 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, -}; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index e01fa9be5f..3f2e8c661f 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -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(1.0), - 1.0, - f_ab, - -in.N, - T, - vec3(1.0), - in.world_position.xyz); + let transmitted_environment_light = bevy_pbr::environment_map::environment_map_light(perceptual_roughness, roughness, vec3(1.0), 1.0, f_ab, -in.N, T, vec3(1.0)); transmitted_light += transmitted_environment_light.diffuse * diffuse_transmissive_color; specular_transmitted_environment_light = transmitted_environment_light.specular * specular_transmissive_color; } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 2e95d840d5..fb4a0a04bd 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -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"] } diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 677378cc90..7b10677784 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -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"), } } diff --git a/examples/3d/reflection_probes.rs b/examples/3d/reflection_probes.rs deleted file mode 100644 index cea7362f2d..0000000000 --- a/examples/3d/reflection_probes.rs +++ /dev/null @@ -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, - - // The specular cubemap that reflects the world, but not the cubes. - specular_environment_map: Handle, - - // The specular cubemap that reflects both the world and the cubes. - specular_reflection_probe: Handle, - - // The skybox cubemap image. This is almost the same as - // `specular_environment_map`. - skybox: Handle, -} - -fn main() { - // Create the app. - App::new() - .add_plugins(DefaultPlugins) - .init_resource::() - .init_resource::() - .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>, - mut materials: ResMut>, - asset_server: Res, - app_status: Res, - cubemaps: Res, -) { - 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, - materials: &mut Assets, -) { - // 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>, - cubemaps: Res, -) { - 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>, - camera_query: Query>, - keyboard: Res>, - mut app_status: ResMut, - cubemaps: Res, -) { - // 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::(); - } - 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>, mut app_status: ResMut) { - 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, - asset_server: Res, -) { - for mut text in text_query.iter_mut() { - *text = app_status.create_text(&asset_server); - } -} - -impl TryFrom for ReflectionMode { - type Error = (); - - fn try_from(value: u32) -> Result { - 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>, - app_status: Res, -) { - 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::(); - - // 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, - } - } -} diff --git a/examples/README.md b/examples/README.md index 4eac95dc55..d31f801676 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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