-
-
Notifications
You must be signed in to change notification settings - Fork 4k
[Merged by Bors] - EnvironmentMapLight, BRDF Improvements #7051
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
Changes from all commits
f392563
09298da
f395858
a303ba4
670961f
9727a26
8e9cede
59d67b2
9d1a22c
12f25de
4f8e4f8
68ba909
4ea9ccb
29c3f71
74dde97
4050e2b
45f2003
f622b17
cc6bd16
84f1cca
26e40b5
2fa31c4
9efea31
7f9033c
6e378e0
9bb52c4
f3cee4e
ff06691
36f2e88
9cacefc
a0bf72e
1ad74c0
8ff3041
4ea1c7b
fc0c5f7
870e524
8b5720a
7ec755e
5be96e1
1d6631b
5e125bc
9f73686
29481f3
cf2621d
4d13868
647b5b3
2245f7f
d555d30
f150f69
562930b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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); | ||
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; | ||
} |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add documentation of the visual effect of the two prefiltered images? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
}, | ||
] | ||
} |
Uh oh!
There was an error while loading. Please reload this page.