Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - EnvironmentMapLight, BRDF Improvements #7051

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f392563
WIP EnvironmentMap
JMS55 Dec 28, 2022
09298da
More EnvionmentMap WIP
JMS55 Dec 28, 2022
f395858
Fix brdf lut
JMS55 Dec 28, 2022
a303ba4
Add PBR example labels
JMS55 Dec 29, 2022
670961f
Implement IBL multiscattering
JMS55 Dec 29, 2022
9727a26
Add docs
JMS55 Dec 29, 2022
8e9cede
Misc doc/rename
JMS55 Dec 29, 2022
59d67b2
Clarify comment
JMS55 Dec 29, 2022
9d1a22c
Don't expose too many items
JMS55 Dec 29, 2022
12f25de
Use analytic fitted BRTF LUT curve
JMS55 Dec 29, 2022
4f8e4f8
Improve PBR example label
JMS55 Dec 30, 2022
68ba909
Remove bad BRDF lut texture
JMS55 Dec 30, 2022
4ea9ccb
Fix broken environment map diffuse
JMS55 Dec 30, 2022
29c3f71
Pacify CI lint
JMS55 Dec 30, 2022
74dde97
Rename to EnvironmentMapLight, doc tweaks
JMS55 Dec 30, 2022
4050e2b
Add specular microfacet multiscattering approximation
JMS55 Dec 31, 2022
45f2003
Doc tweaks
JMS55 Jan 9, 2023
f622b17
Merge remote-tracking branch 'bevy/main' into ibl
JMS55 Jan 14, 2023
cc6bd16
Fix merge
JMS55 Jan 14, 2023
84f1cca
Fix merge
JMS55 Jan 14, 2023
26e40b5
Fix doc link
JMS55 Jan 14, 2023
2fa31c4
Merge commit 'd9265db3447dfe8b74b40de297a3ad7d696f4ece' into ibl
JMS55 Jan 14, 2023
9efea31
Merge commit 'f8feec6ef1a47a6c8a562399b883d92198f02222' into ibl
JMS55 Jan 18, 2023
7f9033c
Merge remote-tracking branch 'bevy/main' into ibl
JMS55 Jan 20, 2023
6e378e0
Merge fix
JMS55 Jan 22, 2023
9bb52c4
Merge commit '38691ee95c7f58918aa13264a8a9c91f52fe1355' into ibl
JMS55 Jan 22, 2023
f3cee4e
Merge commit 'cf612c8349068c2250dddf8190cd9cc5c24763d8' into ibl
JMS55 Jan 24, 2023
ff06691
Move prepass functions to prepass_utils (#7354)
IceSentry Jan 24, 2023
36f2e88
Remove App::add_sub_app (#7290)
james7132 Jan 24, 2023
9cacefc
Fix a few typos in CI messages and comments (#7357)
rparrett Jan 25, 2023
a0bf72e
Merge commit 'bfafa781c1414acafddfb6a99d08a3db669a0612' into ibl
JMS55 Jan 28, 2023
1ad74c0
Merge remote-tracking branch 'bevy/staging' into ibl
JMS55 Jan 30, 2023
8ff3041
Merge fixes
JMS55 Jan 30, 2023
4ea1c7b
Address easy review feedback
JMS55 Jan 30, 2023
fc0c5f7
Merge commit '5d514fb24f2459700f68d8e57d4791bdf5b1b595' into ibl
JMS55 Jan 31, 2023
870e524
Move environment map light to the view bind group
JMS55 Jan 31, 2023
8b5720a
Clippy suggestion
JMS55 Jan 31, 2023
7ec755e
Don't hardcode mip levels
JMS55 Feb 3, 2023
5be96e1
Update docs about removed limitation
JMS55 Feb 4, 2023
1d6631b
Use smaller env map textures
JMS55 Feb 5, 2023
5e125bc
Add EnvironmentMapLight to more examples
JMS55 Feb 5, 2023
9f73686
Merge commit '8f81be984595db14ca3c8c20afbb14a9c503472e' into ibl
JMS55 Feb 6, 2023
29481f3
Replace match with if
JMS55 Feb 7, 2023
cf2621d
Merge commit '978f7cd8bf9423961f663725cba18fb3ed75b46d' into ibl
JMS55 Feb 8, 2023
4d13868
Simplify pbr example
JMS55 Feb 8, 2023
647b5b3
Misc formatting
JMS55 Feb 8, 2023
2245f7f
Change example hdri
JMS55 Feb 8, 2023
d555d30
Fix previous commit
JMS55 Feb 8, 2023
f150f69
Fix CI cargo features
JMS55 Feb 9, 2023
562930b
Change back to pisa environment map
JMS55 Feb 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,14 @@ jobs:
- name: Build bevy
# this uses the same command as when running the example to ensure build is reused
run: |
TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome"
TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome,ktx2,zstd"
- name: Run examples
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome,ktx2,zstd"
sleep 10
done
zip traces.zip trace*.json
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/validation-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ jobs:
shell: bash
# this uses the same command as when running the example to ensure build is reused
run: |
WGPU_BACKEND=dx12 CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing"
WGPU_BACKEND=dx12 CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,ktx2,zstd"

- name: Run examples
shell: bash
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo "running $example_name - "`date`
time WGPU_BACKEND=dx12 CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing"
time WGPU_BACKEND=dx12 CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,ktx2,zstd"
sleep 10
done

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ wasm = false
[[example]]
name = "load_gltf"
path = "examples/3d/load_gltf.rs"
required-features = ["ktx2", "zstd"]

[package.metadata.example.load_gltf]
name = "Load glTF"
Expand Down Expand Up @@ -422,6 +423,7 @@ wasm = true
[[example]]
name = "pbr"
path = "examples/3d/pbr.rs"
required-features = ["ktx2", "zstd"]

[package.metadata.example.pbr]
name = "Physically Based Rendering"
Expand Down Expand Up @@ -1430,6 +1432,7 @@ wasm = true
[[example]]
name = "scene_viewer"
path = "examples/tools/scene_viewer/main.rs"
required-features = ["ktx2", "zstd"]

[package.metadata.example.scene_viewer]
name = "Scene Viewer"
Expand Down
6 changes: 6 additions & 0 deletions assets/environment_maps/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
The pisa_*.ktx2 files were generated from https://github.com/KhronosGroup/glTF-Sample-Environments/blob/master/pisa.hdr using the following tools and commands:
- IBL environment map prefiltering to cubemaps: https://github.com/KhronosGroup/glTF-IBL-Sampler
- Diffuse: ./cli -inputPath pisa.hdr -outCubeMap pisa_diffuse.ktx2 -distribution Lambertian -cubeMapResolution 32
- Specular: ./cli -inputPath pisa.hdr -outCubeMap pisa_specular.ktx2 -distribution GGX -cubeMapResolution 512
- Converting to rgb9e5 format with zstd 'supercompression': https://github.com/DGriffin91/bevy_mod_environment_map_tools
- cargo run --release -- --inputs pisa_diffuse.ktx2,pisa_specular.ktx2 --outputs pisa_diffuse_rgb9e5_zstd.ktx2,pisa_specular_rgb9e5_zstd.ktx2
Binary file not shown.
Binary file not shown.
43 changes: 43 additions & 0 deletions crates/bevy_pbr/src/environment_map/environment_map.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#define_import_path bevy_pbr::environment_map


struct EnvironmentMapLight {
diffuse: vec3<f32>,
specular: vec3<f32>,
};

fn environment_map_light(
perceptual_roughness: f32,
roughness: f32,
diffuse_color: vec3<f32>,
NdotV: f32,
f_ab: vec2<f32>,
N: vec3<f32>,
R: vec3<f32>,
F0: vec3<f32>,
) -> EnvironmentMapLight {

// Split-sum approximation for image based lighting: https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
let smallest_specular_mip_level = textureNumLevels(environment_map_specular) - 1i;
let radiance_level = perceptual_roughness * f32(smallest_specular_mip_level);
let irradiance = textureSample(environment_map_diffuse, environment_map_sampler, N).rgb;
let radiance = textureSampleLevel(environment_map_specular, environment_map_sampler, R, radiance_level).rgb;

// 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 FssEss = kS * f_ab.x + f_ab.y;
let Ess = f_ab.x + f_ab.y;
let Ems = 1.0 - Ess;
let Favg = F0 + (1.0 - F0) / 21.0;
let Fms = FssEss * Favg / (1.0 - Ems * Favg);
cart marked this conversation as resolved.
Show resolved Hide resolved
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;
}
137 changes: 137 additions & 0 deletions crates/bevy_pbr/src/environment_map/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
use bevy_core_pipeline::prelude::Camera3d;
use bevy_ecs::{prelude::Component, query::With};
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_asset::RenderAssets,
render_resource::{
BindGroupEntry, BindGroupLayoutEntry, BindingResource, BindingType, SamplerBindingType,
Shader, ShaderStages, TextureSampleType, TextureViewDimension,
},
texture::{FallbackImageCubemap, Image},
};

pub const ENVIRONMENT_MAP_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 154476556247605696);

pub struct EnvironmentMapPlugin;

impl Plugin for EnvironmentMapPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
ENVIRONMENT_MAP_SHADER_HANDLE,
"environment_map.wgsl",
Shader::from_wgsl
);

app.register_type::<EnvironmentMapLight>()
.add_plugin(ExtractComponentPlugin::<EnvironmentMapLight>::default());
}
}

/// Environment map based ambient lighting representing light from distant scenery.
///
/// When added to a 3D camera, this component adds indirect light
/// to every point of the scene (including inside, enclosed areas) based on
/// an environment cubemap texture. This is similiar 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)]
pub struct EnvironmentMapLight {
pub diffuse_map: Handle<Image>,
pub specular_map: Handle<Image>,
Comment on lines +51 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add documentation of the visual effect of the two prefiltered images?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They correspond to different parts of the BRDF calculations. You need both - it's not like the PBR material with different parameters. These are two parts of the same thing.

}

impl EnvironmentMapLight {
/// Whether or not all textures neccesary to use the environment map
/// have been loaded by the asset server.
pub fn is_loaded(&self, images: &RenderAssets<Image>) -> bool {
images.get(&self.diffuse_map).is_some() && images.get(&self.specular_map).is_some()
}
}

impl ExtractComponent for EnvironmentMapLight {
type Query = &'static Self;
type Filter = With<Camera3d>;
type Out = Self;

fn extract_component(item: bevy_ecs::query::QueryItem<'_, Self::Query>) -> Option<Self::Out> {
Some(item.clone())
}
}

pub fn get_bindings<'a>(
environment_map_light: Option<&EnvironmentMapLight>,
images: &'a RenderAssets<Image>,
fallback_image_cubemap: &'a FallbackImageCubemap,
bindings: [u32; 3],
) -> [BindGroupEntry<'a>; 3] {
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,
),
};

[
BindGroupEntry {
binding: bindings[0],
resource: BindingResource::TextureView(diffuse_map),
},
BindGroupEntry {
binding: bindings[1],
resource: BindingResource::TextureView(specular_map),
},
BindGroupEntry {
binding: bindings[2],
resource: BindingResource::Sampler(&fallback_image_cubemap.sampler),
},
]
}

pub fn get_bind_group_layout_entries(bindings: [u32; 3]) -> [BindGroupLayoutEntry; 3] {
[
BindGroupLayoutEntry {
binding: bindings[0],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: bindings[1],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: bindings[2],
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
]
}
7 changes: 6 additions & 1 deletion crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod wireframe;

mod alpha;
mod bundle;
mod environment_map;
mod fog;
mod light;
mod material;
Expand All @@ -10,8 +11,8 @@ mod prepass;
mod render;

pub use alpha::*;
use bevy_transform::TransformSystem;
pub use bundle::*;
pub use environment_map::EnvironmentMapLight;
pub use fog::*;
pub use light::*;
pub use material::*;
Expand All @@ -27,6 +28,7 @@ pub mod prelude {
DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle,
SpotLightBundle,
},
environment_map::EnvironmentMapLight,
fog::{FogFalloff, FogSettings},
light::{AmbientLight, DirectionalLight, PointLight, SpotLight},
material::{Material, MaterialPlugin},
Expand Down Expand Up @@ -55,6 +57,8 @@ use bevy_render::{
view::{ViewSet, VisibilitySystems},
ExtractSchedule, RenderApp, RenderSet,
};
use bevy_transform::TransformSystem;
use environment_map::EnvironmentMapPlugin;

pub const PBR_TYPES_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1708015359337029744);
Expand Down Expand Up @@ -172,6 +176,7 @@ impl Plugin for PbrPlugin {
prepass_enabled: self.prepass_enabled,
..Default::default()
})
.add_plugin(EnvironmentMapPlugin)
.init_resource::<AmbientLight>()
.init_resource::<GlobalVisiblePointLights>()
.init_resource::<DirectionalLightShadowMap>()
Expand Down
17 changes: 14 additions & 3 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, PrepassPlugin,
SetMeshBindGroup, SetMeshViewBindGroup,
AlphaMode, DrawMesh, EnvironmentMapLight, MeshPipeline, MeshPipelineKey, MeshUniform,
PrepassPlugin, SetMeshBindGroup, SetMeshViewBindGroup,
};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
Expand Down Expand Up @@ -361,10 +361,12 @@ pub fn queue_material_meshes<M: Material>(
render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderMaterials<M>>,
material_meshes: Query<(&Handle<M>, &Handle<Mesh>, &MeshUniform)>,
images: Res<RenderAssets<Image>>,
mut views: Query<(
&ExtractedView,
&VisibleEntities,
Option<&Tonemapping>,
Option<&EnvironmentMapLight>,
&mut RenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>,
&mut RenderPhase<Transparent3d>,
Expand All @@ -376,6 +378,7 @@ pub fn queue_material_meshes<M: Material>(
view,
visible_entities,
tonemapping,
environment_map,
mut opaque_phase,
mut alpha_mask_phase,
mut transparent_phase,
Expand All @@ -388,6 +391,14 @@ pub fn queue_material_meshes<M: Material>(
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
| MeshPipelineKey::from_hdr(view.hdr);

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;
}

if let Some(Tonemapping::Enabled { deband_dither }) = tonemapping {
if !view.hdr {
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
Expand All @@ -397,8 +408,8 @@ pub fn queue_material_meshes<M: Material>(
}
}
}
let rangefinder = view.rangefinder3d();

let rangefinder = view.rangefinder3d();
for visible_entity in &visible_entities.entities {
if let Ok((material_handle, mesh_handle, mesh_uniform)) =
material_meshes.get(*visible_entity)
Expand Down
Loading