Skip to content

Commit 83d6600

Browse files
authored
Implement minimal reflection probes (fixed macOS, iOS, and Android). (#11366)
This pull request re-submits #10057, which was backed out for breaking macOS, iOS, and Android. I've tested this version on macOS and Android and on the iOS simulator. # Objective This pull request implements *reflection probes*, which generalize environment maps to allow for multiple environment maps in the same scene, each of which has an axis-aligned bounding box. This is a standard feature of physically-based renderers and was inspired by [the corresponding feature in Blender's Eevee renderer]. ## Solution This is a minimal implementation of reflection probes that allows artists to define cuboid bounding regions associated with environment maps. For every view, on every frame, a system builds up a list of the nearest 4 reflection probes that are within the view's frustum and supplies that list to the shader. The PBR fragment shader searches through the list, finds the first containing reflection probe, and uses it for indirect lighting, falling back to the view's environment map if none is found. Both forward and deferred renderers are fully supported. A reflection probe is an entity with a pair of components, *LightProbe* and *EnvironmentMapLight* (as well as the standard *SpatialBundle*, to position it in the world). The *LightProbe* component (along with the *Transform*) defines the bounding region, while the *EnvironmentMapLight* component specifies the associated diffuse and specular cubemaps. A frequent question is "why two components instead of just one?" The advantages of this setup are: 1. It's readily extensible to other types of light probes, in particular *irradiance volumes* (also known as ambient cubes or voxel global illumination), which use the same approach of bounding cuboids. With a single component that applies to both reflection probes and irradiance volumes, we can share the logic that implements falloff and blending between multiple light probes between both of those features. 2. It reduces duplication between the existing *EnvironmentMapLight* and these new reflection probes. Systems can treat environment maps attached to cameras the same way they treat environment maps applied to reflection probes if they wish. Internally, we gather up all environment maps in the scene and place them in a cubemap array. At present, this means that all environment maps must have the same size, mipmap count, and texture format. A warning is emitted if this restriction is violated. We could potentially relax this in the future as part of the automatic mipmap generation work, which could easily do texture format conversion as part of its preprocessing. An easy way to generate reflection probe cubemaps is to bake them in Blender and use the `export-blender-gi` tool that's part of the [`bevy-baked-gi`] project. This tool takes a `.blend` file containing baked cubemaps as input and exports cubemap images, pre-filtered with an embedded fork of the [glTF IBL Sampler], alongside a corresponding `.scn.ron` file that the scene spawner can use to recreate the reflection probes. Note that this is intentionally a minimal implementation, to aid reviewability. Known issues are: * Reflection probes are basically unsupported on WebGL 2, because WebGL 2 has no cubemap arrays. (Strictly speaking, you can have precisely one reflection probe in the scene if you have no other cubemaps anywhere, but this isn't very useful.) * Reflection probes have no falloff, so reflections will abruptly change when objects move from one bounding region to another. * As mentioned before, all cubemaps in the world of a given type (diffuse or specular) must have the same size, format, and mipmap count. Future work includes: * Blending between multiple reflection probes. * A falloff/fade-out region so that reflected objects disappear gradually instead of vanishing all at once. * Irradiance volumes for voxel-based global illumination. This should reuse much of the reflection probe logic, as they're both GI techniques based on cuboid bounding regions. * Support for WebGL 2, by breaking batches when reflection probes are used. These issues notwithstanding, I think it's best to land this with roughly the current set of functionality, because this patch is useful as is and adding everything above would make the pull request significantly larger and harder to review. --- ## Changelog ### Added * A new *LightProbe* component is available that specifies a bounding region that an *EnvironmentMapLight* applies to. The combination of a *LightProbe* and an *EnvironmentMapLight* offers *reflection probe* functionality similar to that available in other engines. [the corresponding feature in Blender's Eevee renderer]: https://docs.blender.org/manual/en/latest/render/eevee/light_probes/reflection_cubemaps.html [`bevy-baked-gi`]: https://github.com/pcwalton/bevy-baked-gi [glTF IBL Sampler]: https://github.com/KhronosGroup/glTF-IBL-Sampler
1 parent f795656 commit 83d6600

File tree

24 files changed

+1582
-264
lines changed

24 files changed

+1582
-264
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,6 +2508,17 @@ name = "fallback_image"
25082508
path = "examples/shader/fallback_image.rs"
25092509
doc-scrape-examples = true
25102510

2511+
[[example]]
2512+
name = "reflection_probes"
2513+
path = "examples/3d/reflection_probes.rs"
2514+
doc-scrape-examples = true
2515+
2516+
[package.metadata.example.reflection_probes]
2517+
name = "Reflection Probes"
2518+
description = "Demonstrates reflection probes"
2519+
category = "3D Rendering"
2520+
wasm = false
2521+
25112522
[package.metadata.example.fallback_image]
25122523
hidden = true
25132524

Binary file not shown.

assets/models/cubes/Cubes.glb

5.96 KB
Binary file not shown.

crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
@group(0) @binding(3) var dt_lut_texture: texture_3d<f32>;
88
@group(0) @binding(4) var dt_lut_sampler: sampler;
99
#else
10-
@group(0) @binding(15) var dt_lut_texture: texture_3d<f32>;
11-
@group(0) @binding(16) var dt_lut_sampler: sampler;
10+
@group(0) @binding(16) var dt_lut_texture: texture_3d<f32>;
11+
@group(0) @binding(17) var dt_lut_sampler: sampler;
1212
#endif
1313

1414
fn sample_current_lut(p: vec3<f32>) -> vec3<f32> {

crates/bevy_internal/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ symphonia-vorbis = ["bevy_audio/symphonia-vorbis"]
5858
symphonia-wav = ["bevy_audio/symphonia-wav"]
5959

6060
# Shader formats
61-
shader_format_glsl = ["bevy_render/shader_format_glsl"]
61+
shader_format_glsl = [
62+
"bevy_render/shader_format_glsl",
63+
"bevy_pbr?/shader_format_glsl",
64+
]
6265
shader_format_spirv = ["bevy_render/shader_format_spirv"]
6366

6467
serialize = [

crates/bevy_pbr/Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ keywords = ["bevy"]
1010

1111
[features]
1212
webgl = []
13+
shader_format_glsl = ["naga_oil/glsl"]
1314
pbr_transmission_textures = []
1415

1516
[dependencies]
@@ -34,8 +35,17 @@ fixedbitset = "0.4"
3435
# direct dependency required for derive macro
3536
bytemuck = { version = "1", features = ["derive"] }
3637
radsort = "0.1"
37-
naga_oil = "0.11"
38+
smallvec = "1.6"
3839
thread_local = "1.0"
3940

41+
[target.'cfg(target_arch = "wasm32")'.dependencies]
42+
naga_oil = { version = "0.11" }
43+
44+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
45+
# Omit the `glsl` feature in non-WebAssembly by default.
46+
naga_oil = { version = "0.11", default-features = false, features = [
47+
"test_shader",
48+
] }
49+
4050
[lints]
4151
workspace = true

crates/bevy_pbr/src/deferred/mod.rs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{MeshPipeline, MeshViewBindGroup, ScreenSpaceAmbientOcclusionSettings};
1+
use crate::{
2+
environment_map::RenderViewEnvironmentMaps, MeshPipeline, MeshViewBindGroup,
3+
ScreenSpaceAmbientOcclusionSettings, ViewLightProbesUniformOffset,
4+
};
25
use bevy_app::prelude::*;
36
use bevy_asset::{load_internal_asset, Handle};
47
use bevy_core_pipeline::{
@@ -14,25 +17,17 @@ use bevy_render::{
1417
extract_component::{
1518
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
1619
},
17-
render_asset::RenderAssets,
18-
render_graph::{NodeRunError, RenderGraphContext, ViewNode, ViewNodeRunner},
19-
render_resource::{
20-
binding_types::uniform_buffer, Operations, PipelineCache, RenderPassDescriptor,
21-
},
20+
render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
21+
render_resource::binding_types::uniform_buffer,
22+
render_resource::*,
2223
renderer::{RenderContext, RenderDevice},
23-
texture::Image,
24-
view::{ViewTarget, ViewUniformOffset},
25-
Render, RenderSet,
26-
};
27-
28-
use bevy_render::{
29-
render_graph::RenderGraphApp, render_resource::*, texture::BevyDefault, view::ExtractedView,
30-
RenderApp,
24+
texture::BevyDefault,
25+
view::{ExtractedView, ViewTarget, ViewUniformOffset},
26+
Render, RenderApp, RenderSet,
3127
};
3228

3329
use crate::{
34-
EnvironmentMapLight, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset,
35-
ViewLightsUniformOffset,
30+
MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset, ViewLightsUniformOffset,
3631
};
3732

3833
pub struct DeferredPbrLightingPlugin;
@@ -151,6 +146,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
151146
&'static ViewUniformOffset,
152147
&'static ViewLightsUniformOffset,
153148
&'static ViewFogUniformOffset,
149+
&'static ViewLightProbesUniformOffset,
154150
&'static MeshViewBindGroup,
155151
&'static ViewTarget,
156152
&'static DeferredLightingIdDepthTexture,
@@ -165,6 +161,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
165161
view_uniform_offset,
166162
view_lights_offset,
167163
view_fog_offset,
164+
view_light_probes_offset,
168165
mesh_view_bind_group,
169166
target,
170167
deferred_lighting_id_depth_texture,
@@ -218,6 +215,7 @@ impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
218215
view_uniform_offset.offset,
219216
view_lights_offset.offset,
220217
view_fog_offset.offset,
218+
**view_light_probes_offset,
221219
],
222220
);
223221
render_pass.set_bind_group(1, &bind_group_1, &[]);
@@ -403,28 +401,27 @@ pub fn prepare_deferred_lighting_pipelines(
403401
&ExtractedView,
404402
Option<&Tonemapping>,
405403
Option<&DebandDither>,
406-
Option<&EnvironmentMapLight>,
407404
Option<&ShadowFilteringMethod>,
408405
Has<ScreenSpaceAmbientOcclusionSettings>,
409406
(
410407
Has<NormalPrepass>,
411408
Has<DepthPrepass>,
412409
Has<MotionVectorPrepass>,
413410
),
411+
Has<RenderViewEnvironmentMaps>,
414412
),
415413
With<DeferredPrepass>,
416414
>,
417-
images: Res<RenderAssets<Image>>,
418415
) {
419416
for (
420417
entity,
421418
view,
422419
tonemapping,
423420
dither,
424-
environment_map,
425421
shadow_filter_method,
426422
ssao,
427423
(normal_prepass, depth_prepass, motion_vector_prepass),
424+
has_environment_maps,
428425
) in &views
429426
{
430427
let mut view_key = MeshPipelineKey::from_hdr(view.hdr);
@@ -471,11 +468,10 @@ pub fn prepare_deferred_lighting_pipelines(
471468
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
472469
}
473470

474-
let environment_map_loaded = match environment_map {
475-
Some(environment_map) => environment_map.is_loaded(&images),
476-
None => false,
477-
};
478-
if environment_map_loaded {
471+
// We don't need to check to see whether the environment map is loaded
472+
// because [`gather_light_probes`] already checked that for us before
473+
// adding the [`RenderViewEnvironmentMaps`] component.
474+
if has_environment_maps {
479475
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
480476
}
481477

crates/bevy_pbr/src/environment_map/environment_map.wgsl

Lines changed: 0 additions & 50 deletions
This file was deleted.

crates/bevy_pbr/src/environment_map/mod.rs

Lines changed: 0 additions & 98 deletions
This file was deleted.

crates/bevy_pbr/src/lib.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ pub mod wireframe;
33
mod alpha;
44
mod bundle;
55
pub mod deferred;
6-
mod environment_map;
76
mod extended_material;
87
mod fog;
98
mod light;
9+
mod light_probe;
1010
mod lightmap;
1111
mod material;
1212
mod parallax;
@@ -17,10 +17,10 @@ mod ssao;
1717

1818
pub use alpha::*;
1919
pub use bundle::*;
20-
pub use environment_map::EnvironmentMapLight;
2120
pub use extended_material::*;
2221
pub use fog::*;
2322
pub use light::*;
23+
pub use light_probe::*;
2424
pub use lightmap::*;
2525
pub use material::*;
2626
pub use parallax::*;
@@ -37,9 +37,12 @@ pub mod prelude {
3737
DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle,
3838
SpotLightBundle,
3939
},
40-
environment_map::EnvironmentMapLight,
4140
fog::{FogFalloff, FogSettings},
4241
light::{AmbientLight, DirectionalLight, PointLight, SpotLight},
42+
light_probe::{
43+
environment_map::{EnvironmentMapLight, ReflectionProbeBundle},
44+
LightProbe,
45+
},
4346
material::{Material, MaterialPlugin},
4447
parallax::ParallaxMappingMethod,
4548
pbr_material::StandardMaterial,
@@ -71,7 +74,6 @@ use bevy_render::{
7174
ExtractSchedule, Render, RenderApp, RenderSet,
7275
};
7376
use bevy_transform::TransformSystem;
74-
use environment_map::EnvironmentMapPlugin;
7577

7678
use crate::deferred::DeferredPbrLightingPlugin;
7779

@@ -255,12 +257,12 @@ impl Plugin for PbrPlugin {
255257
..Default::default()
256258
},
257259
ScreenSpaceAmbientOcclusionPlugin,
258-
EnvironmentMapPlugin,
259260
ExtractResourcePlugin::<AmbientLight>::default(),
260261
FogPlugin,
261262
ExtractResourcePlugin::<DefaultOpaqueRendererMethod>::default(),
262263
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
263264
LightmapPlugin,
265+
LightProbePlugin,
264266
))
265267
.configure_sets(
266268
PostUpdate,

0 commit comments

Comments
 (0)