Simplify and modularize code, update envmap info txt
This commit is contained in:
parent
178047b45b
commit
50645ecb29
@ -1,6 +1,7 @@
|
||||
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
|
||||
The the spiaggia_di_mondello_*.ktx2 files were downloaded from https://polyhaven.com/a/spiaggia_di_mondello distributed under the CC0 license by Andreas Mischok.
|
||||
- IBL environment map prefiltering to cubemaps: https://github.com/mate-h/blender-envmap
|
||||
- Imported the GLTF scene into Blender and saved the scene into the blend file
|
||||
- Exported the reflection probe and the environment map as KTX2 files using the command line tool
|
||||
- blender-envmap spiaggia_di_mondello_2k.exr --resolution 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
|
||||
- cargo run --release -- --inputs spiaggia_di_mondello_*.ktx2 --outputs spiaggia_di_mondello_*_rgb9e5_zstd.ktx2
|
||||
|
||||
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
#import bevy_render::maths::{PI, PI_2};
|
||||
#import bevy_pbr::lighting::perceptualRoughnessToRoughness;
|
||||
#import bevy_pbr::utils::{build_orthonormal_basis};
|
||||
|
||||
struct FilteringConstants {
|
||||
mip_level: f32,
|
||||
@ -92,18 +92,6 @@ fn sample_environment(dir: vec3f, level: f32) -> vec4f {
|
||||
return textureSampleLevel(input_texture, input_sampler, cube_uv.uv, cube_uv.face, level);
|
||||
}
|
||||
|
||||
// Calculate tangent space for the given normal
|
||||
fn calculate_tangent_frame(normal: vec3f) -> mat3x3f {
|
||||
// Use a robust method to pick a tangent
|
||||
var up = vec3f(1.0, 0.0, 0.0);
|
||||
if abs(normal.z) < 0.999 {
|
||||
up = vec3f(0.0, 0.0, 1.0);
|
||||
}
|
||||
let tangent = normalize(cross(up, normal));
|
||||
let bitangent = cross(normal, tangent);
|
||||
return mat3x3f(tangent, bitangent, normal);
|
||||
}
|
||||
|
||||
// Hammersley sequence for quasi-random points
|
||||
fn hammersley_2d(i: u32, n: u32) -> vec2f {
|
||||
// Van der Corput sequence
|
||||
@ -125,7 +113,17 @@ fn sample_noise(pixel_coords: vec2u) -> vec4f {
|
||||
return textureSampleLevel(blue_noise_texture, input_sampler, uv, 0.0);
|
||||
}
|
||||
|
||||
// from bevy_pbr/src/render/pbr_lighting.wgsl
|
||||
fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 {
|
||||
// clamp perceptual roughness to prevent precision problems
|
||||
// According to Filament design 0.089 is recommended for mobile
|
||||
// Filament uses 0.045 for non-mobile
|
||||
let clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0);
|
||||
return clampedPerceptualRoughness * clampedPerceptualRoughness;
|
||||
}
|
||||
|
||||
// GGX/Trowbridge-Reitz normal distribution function (D term)
|
||||
// from bevy_pbr/src/render/pbr_lighting.wgsl
|
||||
fn D_GGX(roughness: f32, NdotH: f32) -> f32 {
|
||||
let oneMinusNdotHSquared = 1.0 - NdotH * NdotH;
|
||||
let a = NdotH * roughness;
|
||||
@ -136,14 +134,11 @@ fn D_GGX(roughness: f32, NdotH: f32) -> f32 {
|
||||
|
||||
// Importance sample GGX normal distribution function for a given roughness
|
||||
fn importance_sample_ggx(xi: vec2f, roughness: f32, normal: vec3f) -> vec3f {
|
||||
// Use roughness^2 to ensure correct specular highlights
|
||||
let a = roughness * roughness;
|
||||
|
||||
// Sample in spherical coordinates
|
||||
let phi = PI_2 * xi.x;
|
||||
|
||||
// GGX mapping from uniform random to GGX distribution
|
||||
let cos_theta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
|
||||
let cos_theta = sqrt((1.0 - xi.y) / (1.0 + (roughness * roughness - 1.0) * xi.y));
|
||||
let sin_theta = sqrt(1.0 - cos_theta * cos_theta);
|
||||
|
||||
// Convert to cartesian
|
||||
@ -154,7 +149,7 @@ fn importance_sample_ggx(xi: vec2f, roughness: f32, normal: vec3f) -> vec3f {
|
||||
);
|
||||
|
||||
// Transform from tangent to world space
|
||||
return calculate_tangent_frame(normal) * h;
|
||||
return build_orthonormal_basis(normal) * h;
|
||||
}
|
||||
|
||||
// Calculate LOD for environment map lookup using filtered importance sampling
|
||||
@ -171,7 +166,7 @@ fn calculate_environment_map_lod(pdf: f32, width: f32, samples: f32) -> f32 {
|
||||
|
||||
// Smith geometric shadowing function
|
||||
fn G_Smith(NoV: f32, NoL: f32, roughness: f32) -> f32 {
|
||||
let k = (roughness * roughness) / 2.0;
|
||||
let k = roughness / 2.0;
|
||||
let GGXL = NoL / (NoL * (1.0 - k) + k);
|
||||
let GGXV = NoV / (NoV * (1.0 - k) + k);
|
||||
return GGXL * GGXV;
|
||||
@ -197,8 +192,9 @@ fn generate_radiance_map(@builtin(global_invocation_id) global_id: vec3u) {
|
||||
// For radiance map, view direction = normal for perfect reflection
|
||||
let view = normal;
|
||||
|
||||
// Get the roughness parameter
|
||||
let roughness = constants.roughness;
|
||||
// Convert perceptual roughness to physical microfacet roughness
|
||||
let perceptual_roughness = constants.roughness;
|
||||
let roughness = perceptualRoughnessToRoughness(perceptual_roughness);
|
||||
|
||||
// Get blue noise offset for stratification
|
||||
let vector_noise = sample_noise(coords);
|
||||
@ -292,7 +288,7 @@ fn uniform_sample_sphere(i: u32, normal: vec3f) -> vec3f {
|
||||
z
|
||||
);
|
||||
|
||||
let tangent_frame = calculate_tangent_frame(normal);
|
||||
let tangent_frame = build_orthonormal_basis(normal);
|
||||
return normalize(tangent_frame * dir_uniform);
|
||||
}
|
||||
|
||||
|
||||
@ -51,9 +51,8 @@ use bevy_render::{
|
||||
|
||||
use bevy_light::{EnvironmentMapLight, GeneratedEnvironmentMapLight};
|
||||
|
||||
/// Sphere Cosine Weighted Irradiance shader handle
|
||||
pub const STBN_SPHERE: Handle<Image> = uuid_handle!("3110b545-78e0-48fc-b86e-8bc0ea50fc67");
|
||||
pub const STBN_VEC2: Handle<Image> = uuid_handle!("3110b545-78e0-48fc-b86e-8bc0ea50fc67");
|
||||
/// Handle for Spatio-Temporal Blue Noise texture
|
||||
pub const SBTN: Handle<Image> = uuid_handle!("3110b545-78e0-48fc-b86e-8bc0ea50fc67");
|
||||
|
||||
/// Labels for the environment map generation nodes
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, RenderLabel)]
|
||||
@ -506,13 +505,8 @@ pub fn prepare_generator_bind_groups(
|
||||
render_images: Res<RenderAssets<GpuImage>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// Get blue noise texture
|
||||
let sphere_cosine_weights = render_images
|
||||
.get(&STBN_SPHERE)
|
||||
.expect("Sphere cosine weights texture not loaded");
|
||||
|
||||
let vector2_uniform = render_images
|
||||
.get(&STBN_VEC2)
|
||||
.get(&SBTN)
|
||||
.expect("Vector2 uniform texture not loaded");
|
||||
|
||||
for (entity, textures, env_map_light) in &light_probes {
|
||||
@ -644,8 +638,8 @@ pub fn prepare_generator_bind_groups(
|
||||
sample_count: 1024,
|
||||
roughness: 1.0,
|
||||
blue_noise_size: Vec2::new(
|
||||
sphere_cosine_weights.size.width as f32,
|
||||
sphere_cosine_weights.size.height as f32,
|
||||
vector2_uniform.size.width as f32,
|
||||
vector2_uniform.size.height as f32,
|
||||
),
|
||||
white_point: env_map_light.white_point,
|
||||
};
|
||||
@ -671,7 +665,7 @@ pub fn prepare_generator_bind_groups(
|
||||
(1, &samplers.linear),
|
||||
(2, &irradiance_map),
|
||||
(3, &irradiance_constants_buffer),
|
||||
(4, &sphere_cosine_weights.texture_view),
|
||||
(4, &vector2_uniform.texture_view),
|
||||
)),
|
||||
);
|
||||
|
||||
|
||||
@ -35,18 +35,15 @@ use bevy_render::{
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||
};
|
||||
use bevy_transform::{components::Transform, prelude::GlobalTransform};
|
||||
use generate::{
|
||||
extract_generator_entities, generate_environment_map_light, prepare_generator_bind_groups,
|
||||
prepare_intermediate_textures, GeneratorPipelines, SpdNode, STBN_SPHERE, STBN_VEC2,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use core::{hash::Hash, ops::Deref};
|
||||
|
||||
use crate::{
|
||||
generate::{
|
||||
GeneratorBindGroupLayouts, GeneratorNode, GeneratorSamplers, IrradianceMapNode,
|
||||
RadianceMapNode,
|
||||
extract_generator_entities, generate_environment_map_light, prepare_generator_bind_groups,
|
||||
prepare_intermediate_textures, GeneratorBindGroupLayouts, GeneratorNode,
|
||||
GeneratorPipelines, GeneratorSamplers, IrradianceMapNode, RadianceMapNode, SpdNode, SBTN,
|
||||
},
|
||||
light_probe::environment_map::EnvironmentMapIds,
|
||||
};
|
||||
@ -308,11 +305,8 @@ impl Plugin for LightProbePlugin {
|
||||
embedded_asset!(app, "spd.wgsl");
|
||||
embedded_asset!(app, "copy_mip0.wgsl");
|
||||
|
||||
load_internal_binary_asset!(
|
||||
app,
|
||||
STBN_SPHERE,
|
||||
"noise/sphere_coshemi_gauss1_0.png",
|
||||
|bytes, _: String| Image::from_buffer(
|
||||
load_internal_binary_asset!(app, SBTN, "sbtn_vec2.png", |bytes, _: String| {
|
||||
Image::from_buffer(
|
||||
bytes,
|
||||
ImageType::Extension("png"),
|
||||
CompressedImageFormats::NONE,
|
||||
@ -320,22 +314,8 @@ impl Plugin for LightProbePlugin {
|
||||
ImageSampler::Default,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.expect("Failed to load sphere cosine weighted blue noise texture")
|
||||
);
|
||||
load_internal_binary_asset!(
|
||||
app,
|
||||
STBN_VEC2,
|
||||
"noise/vector2_uniform_gauss1_0.png",
|
||||
|bytes, _: String| Image::from_buffer(
|
||||
bytes,
|
||||
ImageType::Extension("png"),
|
||||
CompressedImageFormats::NONE,
|
||||
false,
|
||||
ImageSampler::Default,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.expect("Failed to load vector2 uniform blue noise texture")
|
||||
);
|
||||
.expect("Failed to load spatio-temporal blue noise texture")
|
||||
});
|
||||
|
||||
app.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new())
|
||||
.add_plugins(SyncComponentPlugin::<GeneratedEnvironmentMapLight>::default())
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB |
BIN
crates/bevy_pbr/src/light_probe/sbtn_vec2.png
Normal file
BIN
crates/bevy_pbr/src/light_probe/sbtn_vec2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@ -68,6 +68,16 @@ fn octahedral_decode_signed(v: vec2<f32>) -> vec3<f32> {
|
||||
return normalize(n);
|
||||
}
|
||||
|
||||
// https://jcgt.org/published/0006/01/01/paper.pdf
|
||||
fn build_orthonormal_basis(normal: vec3<f32>) -> mat3x3<f32> {
|
||||
let sign = select(-1.0, 1.0, normal.z >= 0.0);
|
||||
let a = -1.0 / (sign + normal.z);
|
||||
let b = normal.x * normal.y * a;
|
||||
let tangent = vec3(1.0 + sign * normal.x * normal.x * a, sign * b, -sign * normal.x);
|
||||
let bitangent = vec3(b, sign + normal.y * normal.y * a, -normal.y);
|
||||
return mat3x3(tangent, bitangent, normal);
|
||||
}
|
||||
|
||||
// https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence
|
||||
fn interleaved_gradient_noise(pixel_coordinates: vec2<f32>, frame: u32) -> f32 {
|
||||
let xy = pixel_coordinates + 5.588238 * f32(frame % 64u);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#define_import_path bevy_solari::sampling
|
||||
|
||||
#import bevy_pbr::utils::{rand_f, rand_vec2f, rand_range_u}
|
||||
#import bevy_pbr::utils::{rand_f, rand_vec2f, rand_range_u, build_orthonormal_basis}
|
||||
#import bevy_render::maths::{PI, PI_2}
|
||||
#import bevy_solari::scene_bindings::{trace_ray, RAY_T_MIN, RAY_T_MAX, light_sources, directional_lights, LIGHT_SOURCE_KIND_DIRECTIONAL, resolve_triangle_data_full}
|
||||
|
||||
@ -187,13 +187,3 @@ fn triangle_barycentrics(random: vec2<f32>) -> vec3<f32> {
|
||||
if barycentrics.x + barycentrics.y > 1.0 { barycentrics = 1.0 - barycentrics; }
|
||||
return vec3(1.0 - barycentrics.x - barycentrics.y, barycentrics);
|
||||
}
|
||||
|
||||
// https://jcgt.org/published/0006/01/01/paper.pdf
|
||||
fn build_orthonormal_basis(normal: vec3<f32>) -> mat3x3<f32> {
|
||||
let sign = select(-1.0, 1.0, normal.z >= 0.0);
|
||||
let a = -1.0 / (sign + normal.z);
|
||||
let b = normal.x * normal.y * a;
|
||||
let tangent = vec3(1.0 + sign * normal.x * normal.x * a, sign * b, -sign * normal.x);
|
||||
let bitangent = vec3(b, sign + normal.y * normal.y * a, -normal.y);
|
||||
return mat3x3(tangent, bitangent, normal);
|
||||
}
|
||||
|
||||
@ -53,14 +53,17 @@ struct Cubemaps {
|
||||
// The blurry diffuse cubemap that reflects the world, but not the cubes.
|
||||
diffuse_environment_map: Handle<Image>,
|
||||
|
||||
// The specular cubemap that reflects the world, but not the cubes.
|
||||
// The specular cubemap mip chain that reflects the world, but not the cubes.
|
||||
specular_environment_map: Handle<Image>,
|
||||
|
||||
// The blurry diffuse cubemap that reflects both the world and the cubes.
|
||||
diffuse_reflection_probe: Handle<Image>,
|
||||
|
||||
// The specular cubemap that reflects both the world and the cubes.
|
||||
// The specular cubemap mip chain that reflects both the world and the cubes.
|
||||
specular_reflection_probe: Handle<Image>,
|
||||
|
||||
// Environment map with a single mip level
|
||||
environment_map: Handle<Image>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -164,39 +167,18 @@ fn spawn_reflection_probe(commands: &mut Commands, cubemaps: &Cubemaps) {
|
||||
// 2.0 because the sphere's radius is 1.0 and we want to fully enclose it.
|
||||
Transform::from_scale(Vec3::splat(2.0)),
|
||||
));
|
||||
// spawn directional light for the sun
|
||||
commands.spawn((
|
||||
DirectionalLight {
|
||||
illuminance: 10_000.0,
|
||||
..default()
|
||||
},
|
||||
// Roughly match the position of the sun in the environment map
|
||||
Transform::from_xyz(1.0, 0.5, 0.7).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
));
|
||||
}
|
||||
|
||||
fn spawn_generated_environment_map(commands: &mut Commands, cubemaps: &Cubemaps) {
|
||||
commands.spawn((
|
||||
LightProbe,
|
||||
GeneratedEnvironmentMapLight {
|
||||
// Reuse the specular map for the generated environment map, even
|
||||
// though it already has mip levels. In reality you would use a
|
||||
// cubemap texture without mip levels and generate the mips using
|
||||
// this component by filtering the cubemap on the GPU.
|
||||
environment_map: cubemaps.specular_environment_map.clone(),
|
||||
environment_map: cubemaps.environment_map.clone(),
|
||||
intensity: 5000.0,
|
||||
..default()
|
||||
},
|
||||
Transform::from_scale(Vec3::splat(2.0)),
|
||||
));
|
||||
// spawn directional light
|
||||
commands.spawn((
|
||||
DirectionalLight {
|
||||
illuminance: 30_000.0,
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(1.0, 0.5, 0.7).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
));
|
||||
}
|
||||
|
||||
// Spawns the help text.
|
||||
@ -226,10 +208,7 @@ fn add_environment_map_to_camera(
|
||||
.entity(camera_entity)
|
||||
.insert(create_camera_environment_map_light(&cubemaps))
|
||||
.insert(Skybox {
|
||||
// Reuse 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.
|
||||
image: cubemaps.specular_environment_map.clone(),
|
||||
image: cubemaps.environment_map.clone(),
|
||||
brightness: 5000.0,
|
||||
..default()
|
||||
});
|
||||
@ -384,13 +363,15 @@ impl FromWorld for Cubemaps {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
Cubemaps {
|
||||
diffuse_environment_map: world
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_2k_probe_diffuse_rgb5e9.ktx2"),
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_probe_diffuse.ktx2"),
|
||||
specular_environment_map: world
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_2k_specular_rgb5e9.ktx2"),
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_specular.ktx2"),
|
||||
specular_reflection_probe: world
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_2k_probe_specular_rgb5e9.ktx2"),
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_probe_specular.ktx2"),
|
||||
diffuse_reflection_probe: world
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_2k_probe_diffuse_rgb5e9.ktx2"),
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_probe_diffuse.ktx2"),
|
||||
environment_map: world
|
||||
.load_asset("environment_maps/spiaggia_di_mondello_environment_map.ktx2"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user