Initial commit
This commit is contained in:
parent
8255e6cda9
commit
ffc0613e18
BIN
assets/environment_maps/ballawley_park_1k.ktx2
Normal file
BIN
assets/environment_maps/ballawley_park_1k.ktx2
Normal file
Binary file not shown.
@ -197,6 +197,22 @@ impl Image {
|
|||||||
})
|
})
|
||||||
.map(DynamicImage::ImageRgba8)
|
.map(DynamicImage::ImageRgba8)
|
||||||
}
|
}
|
||||||
|
TextureFormat::Rgba16Float => {
|
||||||
|
use half::f16;
|
||||||
|
let pixel_count = (width * height) as usize;
|
||||||
|
let mut rgba32f_data = Vec::<f32>::with_capacity(pixel_count * 4);
|
||||||
|
for rgba16f in data.chunks_exact(8) {
|
||||||
|
let r = f16::from_bits(u16::from_le_bytes([rgba16f[0], rgba16f[1]])).to_f32();
|
||||||
|
let g = f16::from_bits(u16::from_le_bytes([rgba16f[2], rgba16f[3]])).to_f32();
|
||||||
|
let b = f16::from_bits(u16::from_le_bytes([rgba16f[4], rgba16f[5]])).to_f32();
|
||||||
|
let a = f16::from_bits(u16::from_le_bytes([rgba16f[6], rgba16f[7]])).to_f32();
|
||||||
|
rgba32f_data.push(r);
|
||||||
|
rgba32f_data.push(g);
|
||||||
|
rgba32f_data.push(b);
|
||||||
|
rgba32f_data.push(a);
|
||||||
|
}
|
||||||
|
ImageBuffer::from_raw(width, height, rgba32f_data).map(DynamicImage::ImageRgba32F)
|
||||||
|
}
|
||||||
// Throw and error if conversion isn't supported
|
// Throw and error if conversion isn't supported
|
||||||
texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
|
texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
crates/bevy_pbr/src/atmosphere/bluenoise.ktx2
Normal file
BIN
crates/bevy_pbr/src/atmosphere/bluenoise.ktx2
Normal file
Binary file not shown.
@ -45,6 +45,7 @@ use bevy_ecs::{
|
|||||||
schedule::IntoScheduleConfigs,
|
schedule::IntoScheduleConfigs,
|
||||||
system::{lifetimeless::Read, Query},
|
system::{lifetimeless::Read, Query},
|
||||||
};
|
};
|
||||||
|
use bevy_image::Image;
|
||||||
use bevy_math::{UVec2, UVec3, Vec3};
|
use bevy_math::{UVec2, UVec3, Vec3};
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
|
|||||||
425
crates/bevy_pbr/src/light_probe/environment_filter.wgsl
Normal file
425
crates/bevy_pbr/src/light_probe/environment_filter.wgsl
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
#import bevy_render::maths::{PI, PI_2};
|
||||||
|
#import bevy_pbr::lighting::perceptualRoughnessToRoughness;
|
||||||
|
|
||||||
|
struct PrefilterConstants {
|
||||||
|
mip_level: f32,
|
||||||
|
sample_count: u32,
|
||||||
|
roughness: f32,
|
||||||
|
blue_noise_size: vec2f,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0) var input_texture: texture_2d_array<f32>;
|
||||||
|
@group(0) @binding(1) var input_sampler: sampler;
|
||||||
|
@group(0) @binding(2) var output_texture: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(3) var<uniform> constants: PrefilterConstants;
|
||||||
|
@group(0) @binding(4) var blue_noise_texture: texture_2d<f32>;
|
||||||
|
|
||||||
|
// Tonemapping functions to reduce fireflies
|
||||||
|
fn rcp(x: f32) -> f32 { return 1.0 / x; }
|
||||||
|
fn max3(x: vec3f) -> f32 { return max(x.r, max(x.g, x.b)); }
|
||||||
|
fn tonemap(color: vec3f) -> vec3f {
|
||||||
|
return color / (color + vec3(5000.0));
|
||||||
|
}
|
||||||
|
fn reverse_tonemap(color: vec3f) -> vec3f {
|
||||||
|
return 5000.0 * color / (vec3(1.0) - color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Predefined set of uniform directions
|
||||||
|
fn get_uniform_direction(index: u32) -> vec3f {
|
||||||
|
var dir = vec3f(0.0);
|
||||||
|
|
||||||
|
switch(index % 64u) {
|
||||||
|
case 0u: { dir = vec3f(0.91593, -0.347884, 0.200123); }
|
||||||
|
case 1u: { dir = vec3f(-0.244493, -0.710186, -0.660196); }
|
||||||
|
case 2u: { dir = vec3f(-0.838322, 0.259442, 0.479484); }
|
||||||
|
case 3u: { dir = vec3f(0.245473, 0.891464, -0.380835); }
|
||||||
|
case 4u: { dir = vec3f(0.632533, -0.155099, 0.758846); }
|
||||||
|
case 5u: { dir = vec3f(-0.20644, -0.973183, -0.101474); }
|
||||||
|
case 6u: { dir = vec3f(-0.269471, 0.0483681, -0.961793); }
|
||||||
|
case 7u: { dir = vec3f(0.143331, 0.973557, 0.177887); }
|
||||||
|
case 8u: { dir = vec3f(0.725872, -0.086002, -0.682432); }
|
||||||
|
case 9u: { dir = vec3f(-0.076835, -0.886014, 0.457249); }
|
||||||
|
case 10u: { dir = vec3f(-0.913781, 0.0503775, -0.403071); }
|
||||||
|
case 11u: { dir = vec3f(0.0159914, 0.676129, 0.73661); }
|
||||||
|
case 12u: { dir = vec3f(0.992288, 0.00772121, -0.12371); }
|
||||||
|
case 13u: { dir = vec3f(0.00641109, -0.177892, -0.984029); }
|
||||||
|
case 14u: { dir = vec3f(-0.985566, -0.0665794, 0.155651); }
|
||||||
|
case 15u: { dir = vec3f(-0.0700448, 0.706071, -0.704668); }
|
||||||
|
case 16u: { dir = vec3f(0.89279, 0.117001, 0.435013); }
|
||||||
|
case 17u: { dir = vec3f(0.142896, -0.893697, -0.425307); }
|
||||||
|
case 18u: { dir = vec3f(-0.687174, -0.132142, 0.714374); }
|
||||||
|
case 19u: { dir = vec3f(-0.217251, 0.965143, -0.145946); }
|
||||||
|
case 20u: { dir = vec3f(0.108209, 0.0279573, 0.993735); }
|
||||||
|
case 21u: { dir = vec3f(0.274912, -0.952168, 0.133416); }
|
||||||
|
case 22u: { dir = vec3f(-0.653478, -0.211134, -0.726904); }
|
||||||
|
case 23u: { dir = vec3f(-0.307126, 0.85749, 0.412777); }
|
||||||
|
case 24u: { dir = vec3f(0.831999, 0.327845, -0.447543); }
|
||||||
|
case 25u: { dir = vec3f(0.283463, -0.663772, 0.692138); }
|
||||||
|
case 26u: { dir = vec3f(-0.893939, -0.415437, -0.168182); }
|
||||||
|
case 27u: { dir = vec3f(-0.106605, 0.211719, 0.971499); }
|
||||||
|
case 28u: { dir = vec3f(0.873146, 0.474611, 0.11118); }
|
||||||
|
case 29u: { dir = vec3f(0.332658, -0.572825, -0.74914); }
|
||||||
|
case 30u: { dir = vec3f(-0.781162, -0.487098, 0.390541); }
|
||||||
|
case 31u: { dir = vec3f(-0.490404, 0.734038, -0.469779); }
|
||||||
|
case 32u: { dir = vec3f(0.604084, 0.431641, 0.669902); }
|
||||||
|
case 33u: { dir = vec3f(0.593065, -0.782314, -0.190417); }
|
||||||
|
case 34u: { dir = vec3f(-0.244516, -0.197766, 0.949263); }
|
||||||
|
case 35u: { dir = vec3f(-0.650394, 0.754372, 0.0889437); }
|
||||||
|
case 36u: { dir = vec3f(0.468682, 0.430484, -0.771376); }
|
||||||
|
case 37u: { dir = vec3f(0.647992, -0.666677, 0.368305); }
|
||||||
|
case 38u: { dir = vec3f(-0.604909, -0.626104, -0.492015); }
|
||||||
|
case 39u: { dir = vec3f(-0.564322, 0.511928, 0.647666); }
|
||||||
|
case 40u: { dir = vec3f(0.633455, 0.743985, -0.212653); }
|
||||||
|
case 41u: { dir = vec3f(0.292272, -0.234942, 0.927027); }
|
||||||
|
case 42u: { dir = vec3f(-0.600382, -0.796926, 0.0667077); }
|
||||||
|
case 43u: { dir = vec3f(-0.497216, 0.350652, -0.793612); }
|
||||||
|
case 44u: { dir = vec3f(0.516356, 0.783334, 0.346069); }
|
||||||
|
case 45u: { dir = vec3f(0.729109, -0.451604, -0.514251); }
|
||||||
|
case 46u: { dir = vec3f(-0.389822, -0.675926, 0.62543); }
|
||||||
|
case 47u: { dir = vec3f(-0.856868, 0.458917, -0.234889); }
|
||||||
|
case 48u: { dir = vec3f(0.189162, 0.381537, 0.904791); }
|
||||||
|
case 49u: { dir = vec3f(0.907219, -0.4183, 0.0444719); }
|
||||||
|
case 50u: { dir = vec3f(-0.225508, -0.532484, -0.815848); }
|
||||||
|
case 51u: { dir = vec3f(-0.882371, 0.341401, 0.323833); }
|
||||||
|
case 52u: { dir = vec3f(0.279638, 0.796232, -0.536486); }
|
||||||
|
case 53u: { dir = vec3f(0.759697, -0.242935, 0.603194); }
|
||||||
|
case 54u: { dir = vec3f(-0.265275, -0.929255, -0.257125); }
|
||||||
|
case 55u: { dir = vec3f(-0.455978, 0.114803, 0.882555); }
|
||||||
|
case 56u: { dir = vec3f(0.213508, 0.976688, 0.0222359); }
|
||||||
|
case 57u: { dir = vec3f(0.536034, -0.10141, -0.838084); }
|
||||||
|
case 58u: { dir = vec3f(-0.147707, -0.941925, 0.301597); }
|
||||||
|
case 59u: { dir = vec3f(-0.822975, 0.102675, -0.558722); }
|
||||||
|
case 60u: { dir = vec3f(0.0753368, 0.810439, 0.580958); }
|
||||||
|
case 61u: { dir = vec3f(0.958193, -0.0618399, -0.279361); }
|
||||||
|
case 62u: { dir = vec3f(-0.0168296, -0.509477, 0.860319); }
|
||||||
|
case 63u: { dir = vec3f(-0.999999, 0.00159255, 0.0); }
|
||||||
|
default: { dir = vec3f(0.0, 0.0, 1.0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalize(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert UV and face index to direction vector
|
||||||
|
fn sample_cube_dir(uv: vec2f, face: u32) -> vec3f {
|
||||||
|
// Convert from [0,1] to [-1,1]
|
||||||
|
let uvc = 2.0 * uv - 1.0;
|
||||||
|
|
||||||
|
// Generate direction based on the cube face
|
||||||
|
var dir: vec3f;
|
||||||
|
switch(face) {
|
||||||
|
case 0u: { dir = vec3f( 1.0, -uvc.y, -uvc.x); } // +X
|
||||||
|
case 1u: { dir = vec3f(-1.0, -uvc.y, uvc.x); } // -X
|
||||||
|
case 2u: { dir = vec3f( uvc.x, 1.0, uvc.y); } // +Y
|
||||||
|
case 3u: { dir = vec3f( uvc.x, -1.0, -uvc.y); } // -Y
|
||||||
|
case 4u: { dir = vec3f( uvc.x, -uvc.y, 1.0); } // +Z
|
||||||
|
case 5u: { dir = vec3f(-uvc.x, -uvc.y, -1.0); } // -Z
|
||||||
|
default: { dir = vec3f(0.0); }
|
||||||
|
}
|
||||||
|
return normalize(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert direction vector to cube face UV
|
||||||
|
struct CubeUV {
|
||||||
|
uv: vec2f,
|
||||||
|
face: u32,
|
||||||
|
}
|
||||||
|
fn dir_to_cube_uv(dir: vec3f) -> CubeUV {
|
||||||
|
let abs_dir = abs(dir);
|
||||||
|
var face: u32 = 0u;
|
||||||
|
var uv: vec2f = vec2f(0.0);
|
||||||
|
|
||||||
|
// Find the dominant axis to determine face
|
||||||
|
if (abs_dir.x >= abs_dir.y && abs_dir.x >= abs_dir.z) {
|
||||||
|
// X axis is dominant
|
||||||
|
if (dir.x > 0.0) {
|
||||||
|
face = 0u; // +X
|
||||||
|
uv = vec2f(-dir.z, -dir.y) / dir.x;
|
||||||
|
} else {
|
||||||
|
face = 1u; // -X
|
||||||
|
uv = vec2f(dir.z, -dir.y) / abs_dir.x;
|
||||||
|
}
|
||||||
|
} else if (abs_dir.y >= abs_dir.x && abs_dir.y >= abs_dir.z) {
|
||||||
|
// Y axis is dominant
|
||||||
|
if (dir.y > 0.0) {
|
||||||
|
face = 2u; // +Y
|
||||||
|
uv = vec2f(dir.x, dir.z) / dir.y;
|
||||||
|
} else {
|
||||||
|
face = 3u; // -Y
|
||||||
|
uv = vec2f(dir.x, -dir.z) / abs_dir.y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Z axis is dominant
|
||||||
|
if (dir.z > 0.0) {
|
||||||
|
face = 4u; // +Z
|
||||||
|
uv = vec2f(dir.x, -dir.y) / dir.z;
|
||||||
|
} else {
|
||||||
|
face = 5u; // -Z
|
||||||
|
uv = vec2f(-dir.x, -dir.y) / abs_dir.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from [-1,1] to [0,1]
|
||||||
|
return CubeUV(uv * 0.5 + 0.5, face);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample an environment map with a specific LOD
|
||||||
|
fn sample_environment(dir: vec3f, level: f32) -> vec4f {
|
||||||
|
let cube_uv = dir_to_cube_uv(dir);
|
||||||
|
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
|
||||||
|
var bits = i;
|
||||||
|
bits = (bits << 16u) | (bits >> 16u);
|
||||||
|
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
|
||||||
|
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
||||||
|
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
||||||
|
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
||||||
|
let vdc = f32(bits) * 2.3283064365386963e-10; // 1 / 0x100000000
|
||||||
|
return vec2f(f32(i) / f32(n), vdc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blue noise randomization
|
||||||
|
fn blue_noise_offset(pixel_coords: vec2u) -> vec2f {
|
||||||
|
// Get a stable random offset for this pixel
|
||||||
|
let noise_size = vec2u(u32(constants.blue_noise_size.x), u32(constants.blue_noise_size.y));
|
||||||
|
let noise_coords = pixel_coords % noise_size;
|
||||||
|
return textureSampleLevel(blue_noise_texture, input_sampler, vec2f(noise_coords) / constants.blue_noise_size, 0.0).rg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GGX/Trowbridge-Reitz normal distribution function (D term)
|
||||||
|
fn D_GGX(roughness: f32, NdotH: f32) -> f32 {
|
||||||
|
let oneMinusNdotHSquared = 1.0 - NdotH * NdotH;
|
||||||
|
let a = NdotH * roughness;
|
||||||
|
let k = roughness / (oneMinusNdotHSquared + a * a);
|
||||||
|
let d = k * k * (1.0 / PI);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = 2.0 * PI * 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 sin_theta = sqrt(1.0 - cos_theta * cos_theta);
|
||||||
|
|
||||||
|
// Convert to cartesian
|
||||||
|
let h = vec3f(
|
||||||
|
sin_theta * cos(phi),
|
||||||
|
sin_theta * sin(phi),
|
||||||
|
cos_theta
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transform from tangent to world space
|
||||||
|
return calculate_tangent_frame(normal) * h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate LOD for environment map lookup using filtered importance sampling
|
||||||
|
fn calculate_environment_map_lod(pdf: f32, width: f32, samples: f32) -> f32 {
|
||||||
|
// Solid angle of current sample
|
||||||
|
let omega_s = 1.0 / (samples * pdf);
|
||||||
|
|
||||||
|
// Solid angle of a texel in the environment map
|
||||||
|
let omega_p = 4.0 * PI / (6.0 * width * width);
|
||||||
|
|
||||||
|
// Filtered importance sampling: compute the correct LOD
|
||||||
|
return 0.5 * log2(omega_s / omega_p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smith geometric shadowing function
|
||||||
|
fn G_Smith(NoV: f32, NoL: f32, roughness: f32) -> f32 {
|
||||||
|
let k = (roughness * roughness) / 2.0;
|
||||||
|
let GGXL = NoL / (NoL * (1.0 - k) + k);
|
||||||
|
let GGXV = NoV / (NoV * (1.0 - k) + k);
|
||||||
|
return GGXL * GGXV;
|
||||||
|
}
|
||||||
|
|
||||||
|
@compute
|
||||||
|
@workgroup_size(8, 8, 1)
|
||||||
|
fn generate_radiance_map(@builtin(global_invocation_id) global_id: vec3u) {
|
||||||
|
let size = textureDimensions(output_texture).xy;
|
||||||
|
let invSize = 1.0 / vec2f(size);
|
||||||
|
|
||||||
|
let coords = vec2u(global_id.xy);
|
||||||
|
let face = global_id.z;
|
||||||
|
|
||||||
|
if (any(coords >= size)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert texture coordinates to direction vector
|
||||||
|
let uv = (vec2f(coords) + 0.5) * invSize;
|
||||||
|
let normal = sample_cube_dir(uv, face);
|
||||||
|
|
||||||
|
// For radiance map, view direction = normal for perfect reflection
|
||||||
|
let view = normal;
|
||||||
|
|
||||||
|
// Get the roughness parameter
|
||||||
|
let roughness = constants.roughness;
|
||||||
|
|
||||||
|
// Get blue noise offset for stratification
|
||||||
|
let blue_noise = blue_noise_offset(coords);
|
||||||
|
|
||||||
|
var radiance = vec3f(0.0);
|
||||||
|
var total_weight = 0.0;
|
||||||
|
|
||||||
|
// Skip sampling for mirror reflection (roughness = 0)
|
||||||
|
if (roughness < 0.01) {
|
||||||
|
radiance = sample_environment(normal, 0.0).rgb;
|
||||||
|
textureStore(output_texture, coords, face, vec4f(radiance, 1.0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For higher roughness values, use importance sampling
|
||||||
|
let sample_count = constants.sample_count;
|
||||||
|
|
||||||
|
for (var i = 0u; i < sample_count; i++) {
|
||||||
|
// Get sample coordinates from Hammersley sequence with blue noise offset
|
||||||
|
var xi = hammersley_2d(i, sample_count);
|
||||||
|
xi = fract(xi + blue_noise); // Apply Cranley-Patterson rotation
|
||||||
|
|
||||||
|
// Sample the GGX distribution to get a half vector
|
||||||
|
let half_vector = importance_sample_ggx(xi, roughness, normal);
|
||||||
|
|
||||||
|
// Calculate reflection vector from half vector
|
||||||
|
let light_dir = reflect(-view, half_vector);
|
||||||
|
|
||||||
|
// Calculate weight (N·L)
|
||||||
|
let NoL = dot(normal, light_dir);
|
||||||
|
|
||||||
|
if (NoL > 0.0) {
|
||||||
|
// Calculate values needed for PDF
|
||||||
|
let NoH = dot(normal, half_vector);
|
||||||
|
let VoH = dot(view, half_vector);
|
||||||
|
let NoV = dot(normal, view);
|
||||||
|
|
||||||
|
// Get the geometric shadowing term
|
||||||
|
let G = G_Smith(NoV, NoL, roughness);
|
||||||
|
|
||||||
|
// Probability Distribution Function
|
||||||
|
let pdf = D_GGX(roughness, NoH) * NoH / (4.0 * VoH);
|
||||||
|
|
||||||
|
// Calculate LOD using filtered importance sampling
|
||||||
|
// This is crucial to avoid fireflies and improve quality
|
||||||
|
let width = f32(size.x);
|
||||||
|
let lod = calculate_environment_map_lod(pdf, width, f32(sample_count));
|
||||||
|
|
||||||
|
// Get source mip level - ensure we don't go negative
|
||||||
|
let source_mip = max(0.0, lod);
|
||||||
|
|
||||||
|
// Sample environment map with the light direction
|
||||||
|
var sample_color = sample_environment(light_dir, source_mip).rgb;
|
||||||
|
sample_color = tonemap(sample_color);
|
||||||
|
|
||||||
|
// Accumulate weighted sample, including geometric term
|
||||||
|
radiance += sample_color * NoL * G;
|
||||||
|
total_weight += NoL * G;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize by total weight
|
||||||
|
if (total_weight > 0.0) {
|
||||||
|
radiance = radiance / total_weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse tonemap
|
||||||
|
radiance = reverse_tonemap(radiance);
|
||||||
|
|
||||||
|
// Write result to output texture
|
||||||
|
textureStore(output_texture, coords, face, vec4f(radiance, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@compute
|
||||||
|
@workgroup_size(8, 8, 1)
|
||||||
|
fn generate_irradiance_map(@builtin(global_invocation_id) global_id: vec3u) {
|
||||||
|
let size = textureDimensions(output_texture).xy;
|
||||||
|
let invSize = 1.0 / vec2f(size);
|
||||||
|
|
||||||
|
let coords = vec2u(global_id.xy);
|
||||||
|
let face = global_id.z;
|
||||||
|
|
||||||
|
if (any(coords >= size)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert texture coordinates to direction vector
|
||||||
|
let uv = (vec2f(coords) + 0.5) * invSize;
|
||||||
|
let normal = sample_cube_dir(uv, face);
|
||||||
|
|
||||||
|
// Create tangent space matrix
|
||||||
|
let tangent_frame = calculate_tangent_frame(normal);
|
||||||
|
|
||||||
|
// Get blue noise offset for stratification
|
||||||
|
let blue_noise = blue_noise_offset(coords);
|
||||||
|
|
||||||
|
var irradiance = vec3f(0.0);
|
||||||
|
var total_weight = 0.0;
|
||||||
|
|
||||||
|
let sample_count = min(constants.sample_count, 64u);
|
||||||
|
|
||||||
|
for (var i = 0u; i < sample_count; i++) {
|
||||||
|
// Using a predefined set of directions provides good hemisphere coverage for diffuse
|
||||||
|
var dir = get_uniform_direction((i + u32(coords.x * 7u + coords.y * 11u + face * 5u)) % 64u);
|
||||||
|
|
||||||
|
// Ensure the direction is in the hemisphere defined by the normal
|
||||||
|
let NoL = dot(normal, dir);
|
||||||
|
|
||||||
|
// Flip the direction if it's in the wrong hemisphere
|
||||||
|
if (NoL < 0.0) {
|
||||||
|
dir = -dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recalculate NoL after possible flipping
|
||||||
|
let weight = max(dot(normal, dir), 0.0);
|
||||||
|
|
||||||
|
if (weight > 0.0) {
|
||||||
|
// Lambert PDF
|
||||||
|
let pdf = weight / PI;
|
||||||
|
let width = f32(size.x);
|
||||||
|
|
||||||
|
// Filtered importance sampling
|
||||||
|
let mip_level = clamp(
|
||||||
|
calculate_environment_map_lod(pdf, width, f32(sample_count)),
|
||||||
|
1.0,
|
||||||
|
constants.roughness * 3.0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sample environment with the calculated mip level
|
||||||
|
let sample_color = sample_environment(dir, mip_level).rgb;
|
||||||
|
|
||||||
|
// Accumulate the sample
|
||||||
|
irradiance += sample_color * weight;
|
||||||
|
total_weight += weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize and scale by PI for diffuse BRDF
|
||||||
|
if (total_weight > 0.0) {
|
||||||
|
irradiance = irradiance / total_weight * PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some low-frequency ambient term to avoid completely dark areas
|
||||||
|
irradiance = max(irradiance, vec3f(0.01, 0.01, 0.01));
|
||||||
|
|
||||||
|
// Write result to output texture
|
||||||
|
textureStore(output_texture, coords, face, vec4f(irradiance, 1.0));
|
||||||
|
}
|
||||||
@ -31,6 +31,9 @@ use bevy_render::{
|
|||||||
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||||
};
|
};
|
||||||
use bevy_transform::{components::Transform, prelude::GlobalTransform};
|
use bevy_transform::{components::Transform, prelude::GlobalTransform};
|
||||||
|
use prefilter::{
|
||||||
|
create_environment_map_from_prefilter, extract_prefilter_entities, prepare_prefilter_bind_groups, prepare_prefilter_textures, FilteredEnvironmentMapLight, PrefilterPipelines, SpdNode
|
||||||
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use core::{hash::Hash, ops::Deref};
|
use core::{hash::Hash, ops::Deref};
|
||||||
@ -41,6 +44,7 @@ use self::irradiance_volume::IrradianceVolume;
|
|||||||
|
|
||||||
pub mod environment_map;
|
pub mod environment_map;
|
||||||
pub mod irradiance_volume;
|
pub mod irradiance_volume;
|
||||||
|
pub mod prefilter;
|
||||||
|
|
||||||
/// The maximum number of each type of light probe that each view will consider.
|
/// The maximum number of each type of light probe that each view will consider.
|
||||||
///
|
///
|
||||||
@ -343,7 +347,9 @@ impl Plugin for LightProbePlugin {
|
|||||||
|
|
||||||
app.register_type::<LightProbe>()
|
app.register_type::<LightProbe>()
|
||||||
.register_type::<EnvironmentMapLight>()
|
.register_type::<EnvironmentMapLight>()
|
||||||
.register_type::<IrradianceVolume>();
|
.register_type::<IrradianceVolume>()
|
||||||
|
.add_plugins(ExtractComponentPlugin::<FilteredEnvironmentMapLight>::default())
|
||||||
|
.add_systems(Update, create_environment_map_from_prefilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
fn finish(&self, app: &mut App) {
|
||||||
@ -355,9 +361,26 @@ impl Plugin for LightProbePlugin {
|
|||||||
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new())
|
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new())
|
||||||
.init_resource::<LightProbesBuffer>()
|
.init_resource::<LightProbesBuffer>()
|
||||||
.init_resource::<EnvironmentMapUniformBuffer>()
|
.init_resource::<EnvironmentMapUniformBuffer>()
|
||||||
|
.init_resource::<PrefilterBindGroupLayouts>()
|
||||||
|
.init_resource::<PrefilterSamplers>()
|
||||||
|
.init_resource::<PrefilterPipelines>()
|
||||||
|
.add_render_graph_node::<SpdNode>(Core3d, PrefilterNode::GenerateMipmap)
|
||||||
|
.add_render_graph_node::<RadianceMapNode>(Core3d, PrefilterNode::RadianceMap)
|
||||||
|
.add_render_graph_node::<IrradianceMapNode>(Core3d, PrefilterNode::IrradianceMap)
|
||||||
|
.add_render_graph_edges(
|
||||||
|
Core3d,
|
||||||
|
(
|
||||||
|
Node3d::EndPrepasses,
|
||||||
|
PrefilterNode::GenerateMipmap,
|
||||||
|
PrefilterNode::RadianceMap,
|
||||||
|
PrefilterNode::IrradianceMap,
|
||||||
|
Node3d::StartMainPass,
|
||||||
|
),
|
||||||
|
)
|
||||||
.add_systems(ExtractSchedule, gather_environment_map_uniform)
|
.add_systems(ExtractSchedule, gather_environment_map_uniform)
|
||||||
.add_systems(ExtractSchedule, gather_light_probes::<EnvironmentMapLight>)
|
.add_systems(ExtractSchedule, gather_light_probes::<EnvironmentMapLight>)
|
||||||
.add_systems(ExtractSchedule, gather_light_probes::<IrradianceVolume>)
|
.add_systems(ExtractSchedule, gather_light_probes::<IrradianceVolume>)
|
||||||
|
.add_systems(ExtractSchedule, extract_prefilter_entities.after(create_environment_map_from_prefilter),)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Render,
|
Render,
|
||||||
(upload_light_probes, prepare_environment_uniform_buffer)
|
(upload_light_probes, prepare_environment_uniform_buffer)
|
||||||
|
|||||||
938
crates/bevy_pbr/src/light_probe/prefilter.rs
Normal file
938
crates/bevy_pbr/src/light_probe/prefilter.rs
Normal file
@ -0,0 +1,938 @@
|
|||||||
|
use bevy_asset::{weak_handle, Assets, Handle};
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
query::{QueryState, With, Without},
|
||||||
|
resource::Resource,
|
||||||
|
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
|
||||||
|
world::{FromWorld, World},
|
||||||
|
};
|
||||||
|
use bevy_image::Image;
|
||||||
|
use bevy_math::{Quat, Vec2};
|
||||||
|
use bevy_reflect::Reflect;
|
||||||
|
use bevy_render::{
|
||||||
|
extract_component::ExtractComponent, render_asset::{RenderAssetUsages, RenderAssets}, render_graph::{Node, NodeRunError, RenderGraphContext, RenderLabel}, render_resource::{
|
||||||
|
binding_types::*, AddressMode, BindGroup, BindGroupEntries, BindGroupLayout,
|
||||||
|
BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor,
|
||||||
|
ComputePipelineDescriptor, Extent3d, FilterMode, PipelineCache, Sampler,
|
||||||
|
SamplerBindingType, SamplerDescriptor, Shader, ShaderDefVal, ShaderStages, ShaderType,
|
||||||
|
StorageTextureAccess, Texture, TextureAspect, TextureDescriptor, TextureDimension,
|
||||||
|
TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor,
|
||||||
|
TextureViewDimension, UniformBuffer,
|
||||||
|
}, renderer::{RenderContext, RenderDevice, RenderQueue}, settings::WgpuFeatures, sync_world::RenderEntity, texture::{CachedTexture, GpuImage, TextureCache}, Extract
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::atmosphere;
|
||||||
|
use crate::light_probe::environment_map::EnvironmentMapLight;
|
||||||
|
|
||||||
|
/// A handle to the SPD (Single Pass Downsampling) shader.
|
||||||
|
pub const SPD_SHADER_HANDLE: Handle<Shader> = weak_handle!("5dcf400c-bcb3-49b9-8b7e-80f4117eaf82");
|
||||||
|
|
||||||
|
/// A handle to the environment filter shader.
|
||||||
|
pub const ENVIRONMENT_FILTER_SHADER_HANDLE: Handle<Shader> =
|
||||||
|
weak_handle!("3110b545-78e0-48fc-b86e-8bc0ea50fc67");
|
||||||
|
|
||||||
|
/// Labels for the prefiltering nodes
|
||||||
|
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, RenderLabel)]
|
||||||
|
pub enum PrefilterNode {
|
||||||
|
GenerateMipmap,
|
||||||
|
RadianceMap,
|
||||||
|
IrradianceMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores the bind group layouts for the prefiltering process
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct PrefilterBindGroupLayouts {
|
||||||
|
pub spd: BindGroupLayout,
|
||||||
|
pub radiance: BindGroupLayout,
|
||||||
|
pub irradiance: BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for PrefilterBindGroupLayouts {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
let render_device = world.resource::<RenderDevice>();
|
||||||
|
|
||||||
|
// SPD (Single Pass Downsampling) bind group layout
|
||||||
|
let spd = render_device.create_bind_group_layout(
|
||||||
|
"spd_bind_group_layout",
|
||||||
|
&BindGroupLayoutEntries::with_indices(
|
||||||
|
ShaderStages::COMPUTE,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
texture_2d_array(TextureSampleType::Float { filterable: true }),
|
||||||
|
), // Source texture
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output mip 1
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output mip 2
|
||||||
|
(
|
||||||
|
3,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output mip 3
|
||||||
|
(
|
||||||
|
4,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output mip 4
|
||||||
|
(
|
||||||
|
5,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output mip 5
|
||||||
|
(
|
||||||
|
6,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::ReadWrite,
|
||||||
|
),
|
||||||
|
), // Output mip 6
|
||||||
|
(
|
||||||
|
7,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output mip 7
|
||||||
|
(
|
||||||
|
8,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output mip 8
|
||||||
|
// (
|
||||||
|
// 9,
|
||||||
|
// texture_storage_2d_array(
|
||||||
|
// TextureFormat::Rgba16Float,
|
||||||
|
// StorageTextureAccess::WriteOnly,
|
||||||
|
// ),
|
||||||
|
// ), // Output mip 9
|
||||||
|
// (
|
||||||
|
// 10,
|
||||||
|
// texture_storage_2d_array(
|
||||||
|
// TextureFormat::Rgba16Float,
|
||||||
|
// StorageTextureAccess::WriteOnly,
|
||||||
|
// ),
|
||||||
|
// ), // Output mip 10
|
||||||
|
// (
|
||||||
|
// 11,
|
||||||
|
// texture_storage_2d_array(
|
||||||
|
// TextureFormat::Rgba16Float,
|
||||||
|
// StorageTextureAccess::WriteOnly,
|
||||||
|
// ),
|
||||||
|
// ), // Output mip 11
|
||||||
|
// (
|
||||||
|
// 12,
|
||||||
|
// texture_storage_2d_array(
|
||||||
|
// TextureFormat::Rgba16Float,
|
||||||
|
// StorageTextureAccess::WriteOnly,
|
||||||
|
// ),
|
||||||
|
// ), // Output mip 12
|
||||||
|
(13, sampler(SamplerBindingType::Filtering)), // Linear sampler
|
||||||
|
(14, uniform_buffer::<SpdConstants>(false)), // Uniforms
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Radiance map bind group layout
|
||||||
|
let radiance = render_device.create_bind_group_layout(
|
||||||
|
"radiance_bind_group_layout",
|
||||||
|
&BindGroupLayoutEntries::with_indices(
|
||||||
|
ShaderStages::COMPUTE,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
texture_2d_array(TextureSampleType::Float { filterable: true }),
|
||||||
|
), // Source environment cubemap
|
||||||
|
(1, sampler(SamplerBindingType::Filtering)), // Source sampler
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output specular map
|
||||||
|
(3, uniform_buffer::<PrefilterConstants>(false)), // Uniforms
|
||||||
|
(4, texture_2d(TextureSampleType::Float { filterable: true })), // Blue noise texture
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Irradiance convolution bind group layout
|
||||||
|
let irradiance = render_device.create_bind_group_layout(
|
||||||
|
"irradiance_bind_group_layout",
|
||||||
|
&BindGroupLayoutEntries::with_indices(
|
||||||
|
ShaderStages::COMPUTE,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
texture_2d_array(TextureSampleType::Float { filterable: true }),
|
||||||
|
), // Source environment cubemap
|
||||||
|
(1, sampler(SamplerBindingType::Filtering)), // Source sampler
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
texture_storage_2d_array(
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
), // Output irradiance map
|
||||||
|
(3, uniform_buffer::<PrefilterConstants>(false)), // Uniforms
|
||||||
|
(4, texture_2d(TextureSampleType::Float { filterable: true })), // Blue noise texture
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
spd,
|
||||||
|
radiance,
|
||||||
|
irradiance,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Samplers for the prefiltering process
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct PrefilterSamplers {
|
||||||
|
pub linear: Sampler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for PrefilterSamplers {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
let render_device = world.resource::<RenderDevice>();
|
||||||
|
|
||||||
|
let linear = render_device.create_sampler(&SamplerDescriptor {
|
||||||
|
label: Some("prefilter_linear_sampler"),
|
||||||
|
address_mode_u: AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: AddressMode::ClampToEdge,
|
||||||
|
mag_filter: FilterMode::Linear,
|
||||||
|
min_filter: FilterMode::Linear,
|
||||||
|
mipmap_filter: FilterMode::Linear,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { linear }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pipelines for the prefiltering process
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct PrefilterPipelines {
|
||||||
|
pub spd_first: CachedComputePipelineId,
|
||||||
|
pub spd_second: CachedComputePipelineId,
|
||||||
|
pub radiance: CachedComputePipelineId,
|
||||||
|
pub irradiance: CachedComputePipelineId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for PrefilterPipelines {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
let pipeline_cache = world.resource::<PipelineCache>();
|
||||||
|
let layouts = world.resource::<PrefilterBindGroupLayouts>();
|
||||||
|
|
||||||
|
let render_device = world.resource::<RenderDevice>();
|
||||||
|
let features = render_device.features();
|
||||||
|
let shader_defs = if features.contains(WgpuFeatures::SUBGROUP) {
|
||||||
|
vec![ShaderDefVal::Int("SUBGROUP_SUPPORT".into(), 1)]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Single Pass Downsampling for Base Mip Levels (0-5)
|
||||||
|
let spd_first = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
|
||||||
|
label: Some("spd_first_pipeline".into()),
|
||||||
|
layout: vec![layouts.spd.clone()],
|
||||||
|
push_constant_ranges: vec![],
|
||||||
|
shader: SPD_SHADER_HANDLE,
|
||||||
|
shader_defs: shader_defs.clone(),
|
||||||
|
entry_point: "spd_downsample_first".into(),
|
||||||
|
zero_initialize_workgroup_memory: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Single Pass Downsampling for Remaining Mip Levels (6-12)
|
||||||
|
let spd_second = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
|
||||||
|
label: Some("spd_second_pipeline".into()),
|
||||||
|
layout: vec![layouts.spd.clone()],
|
||||||
|
push_constant_ranges: vec![],
|
||||||
|
shader: SPD_SHADER_HANDLE,
|
||||||
|
shader_defs,
|
||||||
|
entry_point: "spd_downsample_second".into(),
|
||||||
|
zero_initialize_workgroup_memory: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Radiance map for Specular Environment Maps
|
||||||
|
let radiance = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
|
||||||
|
label: Some("radiance_pipeline".into()),
|
||||||
|
layout: vec![layouts.radiance.clone()],
|
||||||
|
push_constant_ranges: vec![],
|
||||||
|
shader: ENVIRONMENT_FILTER_SHADER_HANDLE,
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "generate_radiance_map".into(),
|
||||||
|
zero_initialize_workgroup_memory: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Irradiance map for Diffuse Environment Maps
|
||||||
|
let irradiance = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
|
||||||
|
label: Some("irradiance_pipeline".into()),
|
||||||
|
layout: vec![layouts.irradiance.clone()],
|
||||||
|
push_constant_ranges: vec![],
|
||||||
|
shader: ENVIRONMENT_FILTER_SHADER_HANDLE,
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "generate_irradiance_map".into(),
|
||||||
|
zero_initialize_workgroup_memory: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
spd_first,
|
||||||
|
spd_second,
|
||||||
|
radiance,
|
||||||
|
irradiance,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Reflect, ExtractComponent)]
|
||||||
|
pub struct FilteredEnvironmentMapLight {
|
||||||
|
pub environment_map: Handle<Image>,
|
||||||
|
pub intensity: f32,
|
||||||
|
pub rotation: Quat,
|
||||||
|
pub affects_lightmapped_mesh_diffuse: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FilteredEnvironmentMapLight {
|
||||||
|
fn default() -> Self {
|
||||||
|
FilteredEnvironmentMapLight {
|
||||||
|
environment_map: Handle::default(),
|
||||||
|
intensity: 0.0,
|
||||||
|
rotation: Quat::IDENTITY,
|
||||||
|
affects_lightmapped_mesh_diffuse: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_prefilter_entities(
|
||||||
|
prefilter_query: Extract<
|
||||||
|
Query<(
|
||||||
|
RenderEntity,
|
||||||
|
&FilteredEnvironmentMapLight,
|
||||||
|
&EnvironmentMapLight,
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
mut commands: Commands,
|
||||||
|
render_images: Res<RenderAssets<GpuImage>>,
|
||||||
|
) {
|
||||||
|
for (entity, filtered_env_map, env_map_light) in prefilter_query.iter() {
|
||||||
|
let env_map = render_images
|
||||||
|
.get(&filtered_env_map.environment_map)
|
||||||
|
.expect("Environment map not found");
|
||||||
|
|
||||||
|
let diffuse_map = render_images.get(&env_map_light.diffuse_map);
|
||||||
|
let specular_map = render_images.get(&env_map_light.specular_map);
|
||||||
|
|
||||||
|
// continue if the diffuse map is not found
|
||||||
|
if diffuse_map.is_none() || specular_map.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let diffuse_map = diffuse_map.unwrap();
|
||||||
|
let specular_map = specular_map.unwrap();
|
||||||
|
|
||||||
|
let render_filtered_env_map = RenderEnvironmentMap {
|
||||||
|
environment_map: env_map.clone(),
|
||||||
|
diffuse_map: diffuse_map.clone(),
|
||||||
|
specular_map: specular_map.clone(),
|
||||||
|
intensity: filtered_env_map.intensity,
|
||||||
|
rotation: filtered_env_map.rotation,
|
||||||
|
affects_lightmapped_mesh_diffuse: filtered_env_map.affects_lightmapped_mesh_diffuse,
|
||||||
|
};
|
||||||
|
commands
|
||||||
|
.get_entity(entity)
|
||||||
|
.expect("Entity not synced to render world")
|
||||||
|
.insert(render_filtered_env_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A render-world specific version of FilteredEnvironmentMapLight that uses CachedTexture
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
pub struct RenderEnvironmentMap {
|
||||||
|
pub environment_map: GpuImage,
|
||||||
|
pub diffuse_map: GpuImage,
|
||||||
|
pub specular_map: GpuImage,
|
||||||
|
pub intensity: f32,
|
||||||
|
pub rotation: Quat,
|
||||||
|
pub affects_lightmapped_mesh_diffuse: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct PrefilterTextures {
|
||||||
|
pub environment_map: CachedTexture,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares textures needed for prefiltering
|
||||||
|
pub fn prepare_prefilter_textures(
|
||||||
|
light_probes: Query<Entity, With<RenderEnvironmentMap>>,
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
mut texture_cache: ResMut<TextureCache>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for entity in &light_probes {
|
||||||
|
// Create environment map with 8 mip levels (512x512 -> 1x1)
|
||||||
|
let environment_map = texture_cache.get(
|
||||||
|
&render_device,
|
||||||
|
TextureDescriptor {
|
||||||
|
label: Some("prefilter_environment_map"),
|
||||||
|
size: Extent3d {
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
depth_or_array_layers: 6, // Cubemap faces
|
||||||
|
},
|
||||||
|
mip_level_count: 9, // 512, 256, 128, 64, 32, 16, 8, 4, 2, 1
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format: TextureFormat::Rgba16Float,
|
||||||
|
usage: TextureUsages::TEXTURE_BINDING
|
||||||
|
| TextureUsages::STORAGE_BINDING
|
||||||
|
| TextureUsages::COPY_DST,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
commands
|
||||||
|
.entity(entity)
|
||||||
|
.insert(PrefilterTextures { environment_map });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shader constants for SPD algorithm
|
||||||
|
#[derive(Clone, Copy, ShaderType)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct SpdConstants {
|
||||||
|
mips: u32,
|
||||||
|
inverse_input_size: Vec2,
|
||||||
|
_padding: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constants for prefiltering
|
||||||
|
#[derive(Clone, Copy, ShaderType)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct PrefilterConstants {
|
||||||
|
mip_level: f32,
|
||||||
|
sample_count: u32,
|
||||||
|
roughness: f32,
|
||||||
|
blue_noise_size: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores bind groups for the prefiltering process
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct PrefilterBindGroups {
|
||||||
|
pub spd: BindGroup,
|
||||||
|
pub radiance: Vec<BindGroup>, // One per mip level
|
||||||
|
pub irradiance: BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares bind groups for prefiltering
|
||||||
|
pub fn prepare_prefilter_bind_groups(
|
||||||
|
light_probes: Query<
|
||||||
|
(Entity, &PrefilterTextures, &RenderEnvironmentMap),
|
||||||
|
With<RenderEnvironmentMap>,
|
||||||
|
>,
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
queue: Res<RenderQueue>,
|
||||||
|
layouts: Res<PrefilterBindGroupLayouts>,
|
||||||
|
samplers: Res<PrefilterSamplers>,
|
||||||
|
render_images: Res<RenderAssets<GpuImage>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
// Get blue noise texture
|
||||||
|
let blue_noise_texture = render_images
|
||||||
|
.get(&atmosphere::shaders::BLUENOISE_TEXTURE)
|
||||||
|
.expect("Blue noise texture not loaded");
|
||||||
|
|
||||||
|
for (entity, textures, env_map_light) in &light_probes {
|
||||||
|
// Create SPD bind group
|
||||||
|
let spd_constants = SpdConstants {
|
||||||
|
mips: 8, // Number of mip levels
|
||||||
|
inverse_input_size: Vec2::new(1.0 / 512.0, 1.0 / 512.0), // 1.0 / input size
|
||||||
|
_padding: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut spd_constants_buffer = UniformBuffer::from(spd_constants);
|
||||||
|
spd_constants_buffer.write_buffer(&render_device, &queue);
|
||||||
|
|
||||||
|
let input_env_map =
|
||||||
|
env_map_light
|
||||||
|
.environment_map
|
||||||
|
.texture
|
||||||
|
.create_view(&TextureViewDescriptor {
|
||||||
|
dimension: Some(TextureViewDimension::D2Array),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let spd_bind_group = render_device.create_bind_group(
|
||||||
|
"spd_bind_group",
|
||||||
|
&layouts.spd,
|
||||||
|
&BindGroupEntries::with_indices((
|
||||||
|
(0, &input_env_map),
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 1, &render_device),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
2,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 2, &render_device),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 3, &render_device),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
4,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 4, &render_device),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
5,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 5, &render_device),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
6,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 6, &render_device),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
7,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 7, &render_device),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
8,
|
||||||
|
&create_storage_view(&textures.environment_map.texture, 8, &render_device),
|
||||||
|
),
|
||||||
|
// (
|
||||||
|
// 9,
|
||||||
|
// &create_storage_view(&textures.environment_map.texture, 9, &render_device),
|
||||||
|
// ),
|
||||||
|
// (
|
||||||
|
// 10,
|
||||||
|
// &create_storage_view(&textures.environment_map.texture, 10, &render_device),
|
||||||
|
// ),
|
||||||
|
// (
|
||||||
|
// 11,
|
||||||
|
// &create_storage_view(&textures.environment_map.texture, 11, &render_device),
|
||||||
|
// ),
|
||||||
|
// (
|
||||||
|
// 12,
|
||||||
|
// &create_storage_view(&textures.environment_map.texture, 12, &render_device),
|
||||||
|
// ),
|
||||||
|
(13, &samplers.linear),
|
||||||
|
(14, &spd_constants_buffer),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create radiance map bind groups for each mip level
|
||||||
|
let num_mips = 9;
|
||||||
|
let mut radiance_bind_groups = Vec::with_capacity(num_mips);
|
||||||
|
|
||||||
|
for mip in 0..num_mips {
|
||||||
|
let roughness = mip as f32 / (num_mips - 1) as f32;
|
||||||
|
|
||||||
|
// For higher roughness values, use importance sampling with optimized sample count
|
||||||
|
let sample_count = if roughness < 0.01 {
|
||||||
|
1 // Mirror reflection
|
||||||
|
} else if roughness < 0.25 {
|
||||||
|
16
|
||||||
|
} else if roughness < 0.5 {
|
||||||
|
32
|
||||||
|
} else if roughness < 0.75 {
|
||||||
|
64
|
||||||
|
} else {
|
||||||
|
128
|
||||||
|
};
|
||||||
|
|
||||||
|
let radiance_constants = PrefilterConstants {
|
||||||
|
mip_level: mip as f32,
|
||||||
|
sample_count,
|
||||||
|
roughness,
|
||||||
|
blue_noise_size: Vec2::new(
|
||||||
|
blue_noise_texture.size.width as f32,
|
||||||
|
blue_noise_texture.size.height as f32,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut radiance_constants_buffer = UniformBuffer::from(radiance_constants);
|
||||||
|
radiance_constants_buffer.write_buffer(&render_device, &queue);
|
||||||
|
|
||||||
|
let mip_storage_view = create_storage_view(
|
||||||
|
&env_map_light.specular_map.texture,
|
||||||
|
mip as u32,
|
||||||
|
&render_device,
|
||||||
|
);
|
||||||
|
let bind_group = render_device.create_bind_group(
|
||||||
|
Some(format!("radiance_bind_group_mip_{}", mip).as_str()),
|
||||||
|
&layouts.radiance,
|
||||||
|
&BindGroupEntries::with_indices((
|
||||||
|
(0, &textures.environment_map.default_view),
|
||||||
|
(1, &samplers.linear),
|
||||||
|
(2, &mip_storage_view),
|
||||||
|
(3, &radiance_constants_buffer),
|
||||||
|
(4, &blue_noise_texture.texture_view),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
radiance_bind_groups.push(bind_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create irradiance bind group
|
||||||
|
let irradiance_constants = PrefilterConstants {
|
||||||
|
mip_level: 0.0,
|
||||||
|
sample_count: 64,
|
||||||
|
roughness: 1.0,
|
||||||
|
blue_noise_size: Vec2::new(
|
||||||
|
blue_noise_texture.size.width as f32,
|
||||||
|
blue_noise_texture.size.height as f32,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut irradiance_constants_buffer = UniformBuffer::from(irradiance_constants);
|
||||||
|
irradiance_constants_buffer.write_buffer(&render_device, &queue);
|
||||||
|
|
||||||
|
// create a 2d array view
|
||||||
|
let irradiance_map =
|
||||||
|
env_map_light
|
||||||
|
.diffuse_map
|
||||||
|
.texture
|
||||||
|
.create_view(&TextureViewDescriptor {
|
||||||
|
dimension: Some(TextureViewDimension::D2Array),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let irradiance_bind_group = render_device.create_bind_group(
|
||||||
|
"irradiance_bind_group",
|
||||||
|
&layouts.irradiance,
|
||||||
|
&BindGroupEntries::with_indices((
|
||||||
|
(0, &textures.environment_map.default_view),
|
||||||
|
(1, &samplers.linear),
|
||||||
|
(2, &irradiance_map),
|
||||||
|
(3, &irradiance_constants_buffer),
|
||||||
|
(4, &blue_noise_texture.texture_view),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.entity(entity).insert(PrefilterBindGroups {
|
||||||
|
spd: spd_bind_group,
|
||||||
|
radiance: radiance_bind_groups,
|
||||||
|
irradiance: irradiance_bind_group,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a storage texture view for a specific mip level
|
||||||
|
fn create_storage_view(texture: &Texture, mip: u32, _render_device: &RenderDevice) -> TextureView {
|
||||||
|
texture.create_view(&TextureViewDescriptor {
|
||||||
|
label: Some(format!("storage_view_mip_{}", mip).as_str()),
|
||||||
|
format: Some(texture.format()),
|
||||||
|
dimension: Some(TextureViewDimension::D2Array),
|
||||||
|
aspect: TextureAspect::All,
|
||||||
|
base_mip_level: mip,
|
||||||
|
mip_level_count: Some(1),
|
||||||
|
base_array_layer: 0,
|
||||||
|
array_layer_count: Some(texture.depth_or_array_layers()),
|
||||||
|
usage: Some(TextureUsages::STORAGE_BINDING),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SPD Node implementation that handles both parts of the downsampling (mips 0-12)
|
||||||
|
pub struct SpdNode {
|
||||||
|
query: QueryState<(
|
||||||
|
Entity,
|
||||||
|
Read<PrefilterBindGroups>,
|
||||||
|
Read<RenderEnvironmentMap>,
|
||||||
|
)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for SpdNode {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
query: QueryState::new(world),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node for SpdNode {
|
||||||
|
fn update(&mut self, world: &mut World) {
|
||||||
|
self.query.update_archetypes(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_graph: &mut RenderGraphContext,
|
||||||
|
render_context: &mut RenderContext,
|
||||||
|
world: &World,
|
||||||
|
) -> Result<(), NodeRunError> {
|
||||||
|
let pipeline_cache = world.resource::<PipelineCache>();
|
||||||
|
let pipelines = world.resource::<PrefilterPipelines>();
|
||||||
|
|
||||||
|
// First pass (mips 0-5)
|
||||||
|
let Some(spd_first_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.spd_first)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Second pass (mips 6-12)
|
||||||
|
let Some(spd_second_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.spd_second)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
for (entity, bind_groups, env_map_light) in self.query.iter_manual(world) {
|
||||||
|
// Copy original environment map to mip 0 of the intermediate environment map
|
||||||
|
let textures = world.get::<PrefilterTextures>(entity).unwrap();
|
||||||
|
|
||||||
|
render_context.command_encoder().copy_texture_to_texture(
|
||||||
|
env_map_light.environment_map.texture.as_image_copy(),
|
||||||
|
textures.environment_map.texture.as_image_copy(),
|
||||||
|
Extent3d {
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
depth_or_array_layers: 6,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// First pass - process mips 0-5
|
||||||
|
{
|
||||||
|
let mut compute_pass =
|
||||||
|
render_context
|
||||||
|
.command_encoder()
|
||||||
|
.begin_compute_pass(&ComputePassDescriptor {
|
||||||
|
label: Some("spd_first_pass"),
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
compute_pass.set_pipeline(spd_first_pipeline);
|
||||||
|
compute_pass.set_bind_group(0, &bind_groups.spd, &[]);
|
||||||
|
|
||||||
|
// Calculate the optimal dispatch size based on our shader's workgroup size and thread mapping
|
||||||
|
// The workgroup size is 256x1x1, and our remap_for_wave_reduction maps these threads to a 8x8 block
|
||||||
|
// For a 512x512 texture, we need 512/64 = 8 workgroups in X and 512/64 = 8 workgroups in Y
|
||||||
|
// Each workgroup processes 64x64 pixels (256 threads each handling 16 pixels)
|
||||||
|
compute_pass.dispatch_workgroups(8, 8, 6); // 6 faces of cubemap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass - process mips 6-12
|
||||||
|
{
|
||||||
|
let mut compute_pass =
|
||||||
|
render_context
|
||||||
|
.command_encoder()
|
||||||
|
.begin_compute_pass(&ComputePassDescriptor {
|
||||||
|
label: Some("spd_second_pass"),
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
compute_pass.set_pipeline(spd_second_pipeline);
|
||||||
|
compute_pass.set_bind_group(0, &bind_groups.spd, &[]);
|
||||||
|
|
||||||
|
// Dispatch workgroups - for each face
|
||||||
|
compute_pass.dispatch_workgroups(2, 2, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Radiance map node for generating specular environment maps
|
||||||
|
pub struct RadianceMapNode {
|
||||||
|
query: QueryState<(Entity, Read<PrefilterBindGroups>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for RadianceMapNode {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
query: QueryState::new(world),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node for RadianceMapNode {
|
||||||
|
fn update(&mut self, world: &mut World) {
|
||||||
|
self.query.update_archetypes(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_graph: &mut RenderGraphContext,
|
||||||
|
render_context: &mut RenderContext,
|
||||||
|
world: &World,
|
||||||
|
) -> Result<(), NodeRunError> {
|
||||||
|
let pipeline_cache = world.resource::<PipelineCache>();
|
||||||
|
let pipelines = world.resource::<PrefilterPipelines>();
|
||||||
|
|
||||||
|
let Some(radiance_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.radiance)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, bind_groups) in self.query.iter_manual(world) {
|
||||||
|
let mut compute_pass =
|
||||||
|
render_context
|
||||||
|
.command_encoder()
|
||||||
|
.begin_compute_pass(&ComputePassDescriptor {
|
||||||
|
label: Some("radiance_map_pass"),
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
compute_pass.set_pipeline(radiance_pipeline);
|
||||||
|
|
||||||
|
// Process each mip level
|
||||||
|
for (mip, bind_group) in bind_groups.radiance.iter().enumerate() {
|
||||||
|
compute_pass.set_bind_group(0, bind_group, &[]);
|
||||||
|
|
||||||
|
// Calculate dispatch size based on mip level
|
||||||
|
let mip_size = 512u32 >> mip;
|
||||||
|
let workgroup_count = mip_size.max(8) / 8;
|
||||||
|
|
||||||
|
// Dispatch for all 6 faces
|
||||||
|
compute_pass.dispatch_workgroups(workgroup_count, workgroup_count, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Irradiance Convolution Node
|
||||||
|
pub struct IrradianceMapNode {
|
||||||
|
query: QueryState<(Entity, Read<PrefilterBindGroups>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for IrradianceMapNode {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
query: QueryState::new(world),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node for IrradianceMapNode {
|
||||||
|
fn update(&mut self, world: &mut World) {
|
||||||
|
self.query.update_archetypes(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_graph: &mut RenderGraphContext,
|
||||||
|
render_context: &mut RenderContext,
|
||||||
|
world: &World,
|
||||||
|
) -> Result<(), NodeRunError> {
|
||||||
|
let pipeline_cache = world.resource::<PipelineCache>();
|
||||||
|
let pipelines = world.resource::<PrefilterPipelines>();
|
||||||
|
|
||||||
|
let Some(irradiance_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.irradiance)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, bind_groups) in self.query.iter_manual(world) {
|
||||||
|
let mut compute_pass =
|
||||||
|
render_context
|
||||||
|
.command_encoder()
|
||||||
|
.begin_compute_pass(&ComputePassDescriptor {
|
||||||
|
label: Some("irradiance_map_pass"),
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
compute_pass.set_pipeline(irradiance_pipeline);
|
||||||
|
compute_pass.set_bind_group(0, &bind_groups.irradiance, &[]);
|
||||||
|
|
||||||
|
// Dispatch workgroups - 32x32 texture with 8x8 workgroups
|
||||||
|
compute_pass.dispatch_workgroups(4, 4, 6); // 6 faces
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System that creates an `EnvironmentMapLight` component from the prefiltered textures
|
||||||
|
pub fn create_environment_map_from_prefilter(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut images: ResMut<Assets<Image>>,
|
||||||
|
query: Query<(Entity, &FilteredEnvironmentMapLight), Without<EnvironmentMapLight>>,
|
||||||
|
) {
|
||||||
|
for (entity, filtered_env_map) in &query {
|
||||||
|
// Create a placeholder for the irradiance map
|
||||||
|
let mut diffuse = Image::new_fill(
|
||||||
|
Extent3d {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
depth_or_array_layers: 6,
|
||||||
|
},
|
||||||
|
TextureDimension::D2,
|
||||||
|
&[0; 8],
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
RenderAssetUsages::all(),
|
||||||
|
);
|
||||||
|
|
||||||
|
diffuse.texture_descriptor.usage =
|
||||||
|
TextureUsages::TEXTURE_BINDING | TextureUsages::STORAGE_BINDING;
|
||||||
|
|
||||||
|
diffuse.texture_view_descriptor = Some(TextureViewDescriptor {
|
||||||
|
dimension: Some(TextureViewDimension::Cube),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let diffuse_handle = images.add(diffuse);
|
||||||
|
|
||||||
|
// Create a placeholder for the specular map
|
||||||
|
let mut specular = Image::new_fill(
|
||||||
|
Extent3d {
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
depth_or_array_layers: 6,
|
||||||
|
},
|
||||||
|
TextureDimension::D2,
|
||||||
|
&[0; 8],
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
RenderAssetUsages::all(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up for mipmaps
|
||||||
|
specular.texture_descriptor.usage =
|
||||||
|
TextureUsages::TEXTURE_BINDING | TextureUsages::STORAGE_BINDING;
|
||||||
|
specular.texture_descriptor.mip_level_count = 9;
|
||||||
|
|
||||||
|
// When setting mip_level_count, we need to allocate appropriate data size
|
||||||
|
// For GPU-generated mipmaps, we can set data to None since the GPU will generate the data
|
||||||
|
specular.data = None;
|
||||||
|
|
||||||
|
specular.texture_view_descriptor = Some(TextureViewDescriptor {
|
||||||
|
dimension: Some(TextureViewDimension::Cube),
|
||||||
|
mip_level_count: Some(9),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let specular_handle = images.add(specular);
|
||||||
|
|
||||||
|
// Add the EnvironmentMapLight component with the placeholder handles
|
||||||
|
commands.entity(entity).insert(EnvironmentMapLight {
|
||||||
|
diffuse_map: diffuse_handle,
|
||||||
|
specular_map: specular_handle,
|
||||||
|
intensity: filtered_env_map.intensity,
|
||||||
|
rotation: filtered_env_map.rotation,
|
||||||
|
affects_lightmapped_mesh_diffuse: filtered_env_map.affects_lightmapped_mesh_diffuse,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
398
crates/bevy_pbr/src/light_probe/spd.wgsl
Normal file
398
crates/bevy_pbr/src/light_probe/spd.wgsl
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
// Ported from https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/c16b1d286b5b438b75da159ab51ff426bacea3d1/sdk/include/FidelityFX/gpu/spd/ffx_spd.h
|
||||||
|
|
||||||
|
@group(0) @binding(0) var mip_0: texture_2d_array<f32>;
|
||||||
|
@group(0) @binding(1) var mip_1: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(2) var mip_2: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(3) var mip_3: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(4) var mip_4: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(5) var mip_5: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(6) var mip_6: texture_storage_2d_array<rgba16float, read_write>;
|
||||||
|
@group(0) @binding(7) var mip_7: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(8) var mip_8: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(9) var mip_9: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(10) var mip_10: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(11) var mip_11: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(12) var mip_12: texture_storage_2d_array<rgba16float, write>;
|
||||||
|
@group(0) @binding(13) var sampler_linear_clamp: sampler;
|
||||||
|
@group(0) @binding(14) var<uniform> constants: Constants;
|
||||||
|
struct Constants { mips: u32, inverse_input_size: vec2f }
|
||||||
|
|
||||||
|
var<workgroup> spd_intermediate_r: array<array<f32, 16>, 16>;
|
||||||
|
var<workgroup> spd_intermediate_g: array<array<f32, 16>, 16>;
|
||||||
|
var<workgroup> spd_intermediate_b: array<array<f32, 16>, 16>;
|
||||||
|
var<workgroup> spd_intermediate_a: array<array<f32, 16>, 16>;
|
||||||
|
|
||||||
|
@compute
|
||||||
|
@workgroup_size(256, 1, 1)
|
||||||
|
fn spd_downsample_first(
|
||||||
|
@builtin(workgroup_id) workgroup_id: vec3u,
|
||||||
|
@builtin(local_invocation_index) local_invocation_index: u32,
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
@builtin(subgroup_invocation_id) subgroup_invocation_id: u32,
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
|
#ifndef SUBGROUP_SUPPORT
|
||||||
|
let subgroup_invocation_id = 0u;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let sub_xy = remap_for_wave_reduction(local_invocation_index % 64u);
|
||||||
|
let x = sub_xy.x + 8u * ((local_invocation_index >> 6u) % 2u);
|
||||||
|
let y = sub_xy.y + 8u * (local_invocation_index >> 7u);
|
||||||
|
|
||||||
|
spd_downsample_mips_0_1(x, y, workgroup_id.xy, local_invocation_index, constants.mips, workgroup_id.z, subgroup_invocation_id);
|
||||||
|
|
||||||
|
spd_downsample_next_four(x, y, workgroup_id.xy, local_invocation_index, 2u, constants.mips, workgroup_id.z, subgroup_invocation_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Once wgpu supports globallycoherent buffers, make it actually a single pass
|
||||||
|
@compute
|
||||||
|
@workgroup_size(256, 1, 1)
|
||||||
|
fn spd_downsample_second(
|
||||||
|
@builtin(workgroup_id) workgroup_id: vec3u,
|
||||||
|
@builtin(local_invocation_index) local_invocation_index: u32,
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
@builtin(subgroup_invocation_id) subgroup_invocation_id: u32,
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
|
#ifndef SUBGROUP_SUPPORT
|
||||||
|
let subgroup_invocation_id = 0u;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
let sub_xy = remap_for_wave_reduction(local_invocation_index % 64u);
|
||||||
|
let x = sub_xy.x + 8u * ((local_invocation_index >> 6u) % 2u);
|
||||||
|
let y = sub_xy.y + 8u * (local_invocation_index >> 7u);
|
||||||
|
|
||||||
|
spd_downsample_mips_6_7(x, y, constants.mips, workgroup_id.z);
|
||||||
|
|
||||||
|
spd_downsample_next_four(x, y, vec2(0u), local_invocation_index, 8u, constants.mips, workgroup_id.z, subgroup_invocation_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, mips: u32, slice: u32, subgroup_invocation_id: u32) {
|
||||||
|
var v: array<vec4f, 4>;
|
||||||
|
|
||||||
|
var tex = (workgroup_id * 64u) + vec2(x * 2u, y * 2u);
|
||||||
|
var pix = (workgroup_id * 32u) + vec2(x, y);
|
||||||
|
v[0] = spd_reduce_load_source_image(tex, slice);
|
||||||
|
spd_store(pix, v[0], 0u, slice);
|
||||||
|
|
||||||
|
tex = (workgroup_id * 64u) + vec2(x * 2u + 32u, y * 2u);
|
||||||
|
pix = (workgroup_id * 32u) + vec2(x + 16u, y);
|
||||||
|
v[1] = spd_reduce_load_source_image(tex, slice);
|
||||||
|
spd_store(pix, v[1], 0u, slice);
|
||||||
|
|
||||||
|
tex = (workgroup_id * 64u) + vec2(x * 2u, y * 2u + 32u);
|
||||||
|
pix = (workgroup_id * 32u) + vec2(x, y + 16u);
|
||||||
|
v[2] = spd_reduce_load_source_image(tex, slice);
|
||||||
|
spd_store(pix, v[2], 0u, slice);
|
||||||
|
|
||||||
|
tex = (workgroup_id * 64u) + vec2(x * 2u + 32u, y * 2u + 32u);
|
||||||
|
pix = (workgroup_id * 32u) + vec2(x + 16u, y + 16u);
|
||||||
|
v[3] = spd_reduce_load_source_image(tex, slice);
|
||||||
|
spd_store(pix, v[3], 0u, slice);
|
||||||
|
|
||||||
|
if mips <= 1u { return; }
|
||||||
|
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
v[0] = spd_reduce_quad(v[0], subgroup_invocation_id);
|
||||||
|
v[1] = spd_reduce_quad(v[1], subgroup_invocation_id);
|
||||||
|
v[2] = spd_reduce_quad(v[2], subgroup_invocation_id);
|
||||||
|
v[3] = spd_reduce_quad(v[3], subgroup_invocation_id);
|
||||||
|
|
||||||
|
if local_invocation_index % 4u == 0u {
|
||||||
|
spd_store((workgroup_id * 16u) + vec2(x / 2u, y / 2u), v[0], 1u, slice);
|
||||||
|
spd_store_intermediate(x / 2u, y / 2u, v[0]);
|
||||||
|
|
||||||
|
spd_store((workgroup_id * 16u) + vec2(x / 2u + 8u, y / 2u), v[1], 1u, slice);
|
||||||
|
spd_store_intermediate(x / 2u + 8u, y / 2u, v[1]);
|
||||||
|
|
||||||
|
spd_store((workgroup_id * 16u) + vec2(x / 2u, y / 2u + 8u), v[2], 1u, slice);
|
||||||
|
spd_store_intermediate(x / 2u, y / 2u + 8u, v[2]);
|
||||||
|
|
||||||
|
spd_store((workgroup_id * 16u) + vec2(x / 2u + 8u, y / 2u + 8u), v[3], 1u, slice);
|
||||||
|
spd_store_intermediate(x / 2u + 8u, y / 2u + 8u, v[3]);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
for (var i = 0u; i < 4u; i++) {
|
||||||
|
spd_store_intermediate(x, y, v[i]);
|
||||||
|
workgroupBarrier();
|
||||||
|
if local_invocation_index < 64u {
|
||||||
|
v[i] = spd_reduce_intermediate(
|
||||||
|
vec2(x * 2u + 0u, y * 2u + 0u),
|
||||||
|
vec2(x * 2u + 1u, y * 2u + 0u),
|
||||||
|
vec2(x * 2u + 0u, y * 2u + 1u),
|
||||||
|
vec2(x * 2u + 1u, y * 2u + 1u),
|
||||||
|
);
|
||||||
|
spd_store(vec2(workgroup_id * 16) + vec2(x + (i % 2u) * 8u, y + (i / 2u) * 8u), v[i], 1u, slice);
|
||||||
|
}
|
||||||
|
workgroupBarrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
if local_invocation_index < 64u {
|
||||||
|
spd_store_intermediate(x + 0u, y + 0u, v[0]);
|
||||||
|
spd_store_intermediate(x + 8u, y + 0u, v[1]);
|
||||||
|
spd_store_intermediate(x + 0u, y + 8u, v[2]);
|
||||||
|
spd_store_intermediate(x + 8u, y + 8u, v[3]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32, subgroup_invocation_id: u32) {
|
||||||
|
if mips <= base_mip { return; }
|
||||||
|
workgroupBarrier();
|
||||||
|
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice, subgroup_invocation_id);
|
||||||
|
|
||||||
|
if mips <= base_mip + 1u { return; }
|
||||||
|
workgroupBarrier();
|
||||||
|
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1u, slice, subgroup_invocation_id);
|
||||||
|
|
||||||
|
if mips <= base_mip + 2u { return; }
|
||||||
|
workgroupBarrier();
|
||||||
|
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2u, slice, subgroup_invocation_id);
|
||||||
|
|
||||||
|
if mips <= base_mip + 3u { return; }
|
||||||
|
workgroupBarrier();
|
||||||
|
spd_downsample_mip_5(x, y, workgroup_id, local_invocation_index, base_mip + 3u, slice, subgroup_invocation_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) {
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
var v = spd_load_intermediate(x, y);
|
||||||
|
v = spd_reduce_quad(v, subgroup_invocation_id);
|
||||||
|
if local_invocation_index % 4u == 0u {
|
||||||
|
spd_store((workgroup_id * 8u) + vec2(x / 2u, y / 2u), v, base_mip, slice);
|
||||||
|
spd_store_intermediate(x + (y / 2u) % 2u, y, v);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if local_invocation_index < 64u {
|
||||||
|
let v = spd_reduce_intermediate(
|
||||||
|
vec2(x * 2u + 0u, y * 2u + 0u),
|
||||||
|
vec2(x * 2u + 1u, y * 2u + 0u),
|
||||||
|
vec2(x * 2u + 0u, y * 2u + 1u),
|
||||||
|
vec2(x * 2u + 1u, y * 2u + 1u),
|
||||||
|
);
|
||||||
|
spd_store((workgroup_id * 8u) + vec2(x, y), v, base_mip, slice);
|
||||||
|
spd_store_intermediate(x * 2u + y % 2u, y * 2u, v);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) {
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
if local_invocation_index < 64u {
|
||||||
|
var v = spd_load_intermediate(x * 2u + y % 2u, y * 2u);
|
||||||
|
v = spd_reduce_quad(v, subgroup_invocation_id);
|
||||||
|
if local_invocation_index % 4u == 0u {
|
||||||
|
spd_store((workgroup_id * 4u) + vec2(x / 2u, y / 2u), v, base_mip, slice);
|
||||||
|
spd_store_intermediate(x * 2u + y / 2u, y * 2u, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if local_invocation_index < 16u {
|
||||||
|
let v = spd_reduce_intermediate(
|
||||||
|
vec2(x * 4u + 0u + 0u, y * 4u + 0u),
|
||||||
|
vec2(x * 4u + 2u + 0u, y * 4u + 0u),
|
||||||
|
vec2(x * 4u + 0u + 1u, y * 4u + 2u),
|
||||||
|
vec2(x * 4u + 2u + 1u, y * 4u + 2u),
|
||||||
|
);
|
||||||
|
spd_store((workgroup_id * 4u) + vec2(x, y), v, base_mip, slice);
|
||||||
|
spd_store_intermediate(x * 4u + y, y * 4u, v);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) {
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
if local_invocation_index < 16u {
|
||||||
|
var v = spd_load_intermediate(x * 4u + y, y * 4u);
|
||||||
|
v = spd_reduce_quad(v, subgroup_invocation_id);
|
||||||
|
if local_invocation_index % 4u == 0u {
|
||||||
|
spd_store((workgroup_id * 2u) + vec2(x / 2u, y / 2u), v, base_mip, slice);
|
||||||
|
spd_store_intermediate(x / 2u + y, 0u, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if local_invocation_index < 4u {
|
||||||
|
let v = spd_reduce_intermediate(
|
||||||
|
vec2(x * 8u + 0u + 0u + y * 2u, y * 8u + 0u),
|
||||||
|
vec2(x * 8u + 4u + 0u + y * 2u, y * 8u + 0u),
|
||||||
|
vec2(x * 8u + 0u + 1u + y * 2u, y * 8u + 4u),
|
||||||
|
vec2(x * 8u + 4u + 1u + y * 2u, y * 8u + 4u),
|
||||||
|
);
|
||||||
|
spd_store((workgroup_id * 2u) + vec2(x, y), v, base_mip, slice);
|
||||||
|
spd_store_intermediate(x + y * 2u, 0u, v);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_downsample_mip_5(x: u32, y: u32, workgroup_id: vec2u, local_invocation_index: u32, base_mip: u32, slice: u32, subgroup_invocation_id: u32) {
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
if local_invocation_index < 4u {
|
||||||
|
var v = spd_load_intermediate(local_invocation_index, 0u);
|
||||||
|
v = spd_reduce_quad(v, subgroup_invocation_id);
|
||||||
|
if local_invocation_index % 4u == 0u {
|
||||||
|
spd_store(workgroup_id, v, base_mip, slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if local_invocation_index < 1u {
|
||||||
|
let v = spd_reduce_intermediate(vec2(0u, 0u), vec2(1u, 0u), vec2(2u, 0u), vec2(3u, 0u));
|
||||||
|
spd_store(workgroup_id, v, base_mip, slice);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_downsample_mips_6_7(x: u32, y: u32, mips: u32, slice: u32) {
|
||||||
|
var tex = vec2(x * 4u + 0u, y * 4u + 0u);
|
||||||
|
var pix = vec2(x * 2u + 0u, y * 2u + 0u);
|
||||||
|
let v0 = spd_reduce_load_4(
|
||||||
|
vec2(x * 4u + 0u, y * 4u + 0u),
|
||||||
|
vec2(x * 4u + 1u, y * 4u + 0u),
|
||||||
|
vec2(x * 4u + 0u, y * 4u + 1u),
|
||||||
|
vec2(x * 4u + 1u, y * 4u + 1u),
|
||||||
|
slice
|
||||||
|
);
|
||||||
|
spd_store(pix, v0, 6u, slice);
|
||||||
|
|
||||||
|
tex = vec2(x * 4u + 2u, y * 4u + 0u);
|
||||||
|
pix = vec2(x * 2u + 1u, y * 2u + 0u);
|
||||||
|
let v1 = spd_reduce_load_4(
|
||||||
|
vec2(x * 4u + 2u, y * 4u + 0u),
|
||||||
|
vec2(x * 4u + 3u, y * 4u + 0u),
|
||||||
|
vec2(x * 4u + 2u, y * 4u + 1u),
|
||||||
|
vec2(x * 4u + 3u, y * 4u + 1u),
|
||||||
|
slice
|
||||||
|
);
|
||||||
|
spd_store(pix, v1, 6u, slice);
|
||||||
|
|
||||||
|
tex = vec2(x * 4u + 0u, y * 4u + 2u);
|
||||||
|
pix = vec2(x * 2u + 0u, y * 2u + 1u);
|
||||||
|
let v2 = spd_reduce_load_4(
|
||||||
|
vec2(x * 4u + 0u, y * 4u + 2u),
|
||||||
|
vec2(x * 4u + 1u, y * 4u + 2u),
|
||||||
|
vec2(x * 4u + 0u, y * 4u + 3u),
|
||||||
|
vec2(x * 4u + 1u, y * 4u + 3u),
|
||||||
|
slice
|
||||||
|
);
|
||||||
|
spd_store(pix, v2, 6u, slice);
|
||||||
|
|
||||||
|
tex = vec2(x * 4u + 2u, y * 4u + 2u);
|
||||||
|
pix = vec2(x * 2u + 1u, y * 2u + 1u);
|
||||||
|
let v3 = spd_reduce_load_4(
|
||||||
|
vec2(x * 4u + 2u, y * 4u + 2u),
|
||||||
|
vec2(x * 4u + 3u, y * 4u + 2u),
|
||||||
|
vec2(x * 4u + 2u, y * 4u + 3u),
|
||||||
|
vec2(x * 4u + 3u, y * 4u + 3u),
|
||||||
|
slice
|
||||||
|
);
|
||||||
|
spd_store(pix, v3, 6u, slice);
|
||||||
|
|
||||||
|
if mips < 7u { return; }
|
||||||
|
|
||||||
|
let v = spd_reduce_4(v0, v1, v2, v3);
|
||||||
|
spd_store(vec2(x, y), v, 7u, slice);
|
||||||
|
spd_store_intermediate(x, y, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remap_for_wave_reduction(a: u32) -> vec2u {
|
||||||
|
// This function maps linear thread IDs to 2D coordinates in a special pattern
|
||||||
|
// to ensure that neighboring threads process neighboring pixels
|
||||||
|
// For example, this transforms linear thread IDs 0,1,2,3 into a 2×2 square
|
||||||
|
|
||||||
|
// Extract bits to form the X and Y coordinates
|
||||||
|
let x = insertBits(extractBits(a, 2u, 3u), a, 0u, 1u);
|
||||||
|
let y = insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u);
|
||||||
|
|
||||||
|
return vec2u(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_reduce_load_source_image(uv: vec2u, slice: u32) -> vec4f {
|
||||||
|
let texture_coord = (vec2f(uv) + 0.5) * constants.inverse_input_size;
|
||||||
|
|
||||||
|
let result = textureSampleLevel(mip_0, sampler_linear_clamp, texture_coord, slice, 0.0);
|
||||||
|
|
||||||
|
#ifdef SRGB_CONVERSION
|
||||||
|
return vec4(
|
||||||
|
srgb_from_linear(result.r),
|
||||||
|
srgb_from_linear(result.g),
|
||||||
|
srgb_from_linear(result.b),
|
||||||
|
result.a
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
return result;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_store(pix: vec2u, value: vec4f, mip: u32, slice: u32) {
|
||||||
|
if mip >= constants.mips { return; }
|
||||||
|
switch mip {
|
||||||
|
case 0u: { textureStore(mip_1, pix, slice, value); }
|
||||||
|
case 1u: { textureStore(mip_2, pix, slice, value); }
|
||||||
|
case 2u: { textureStore(mip_3, pix, slice, value); }
|
||||||
|
case 3u: { textureStore(mip_4, pix, slice, value); }
|
||||||
|
case 4u: { textureStore(mip_5, pix, slice, value); }
|
||||||
|
case 5u: { textureStore(mip_6, pix, slice, value); }
|
||||||
|
case 6u: { textureStore(mip_7, pix, slice, value); }
|
||||||
|
case 7u: { textureStore(mip_8, pix, slice, value); }
|
||||||
|
// case 8u: { textureStore(mip_9, pix, slice, value); }
|
||||||
|
// case 9u: { textureStore(mip_10, pix, slice, value); }
|
||||||
|
// case 10u: { textureStore(mip_11, pix, slice, value); }
|
||||||
|
// case 11u: { textureStore(mip_12, pix, slice, value); }
|
||||||
|
default: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_store_intermediate(x: u32, y: u32, value: vec4f) {
|
||||||
|
spd_intermediate_r[x][y] = value.x;
|
||||||
|
spd_intermediate_g[x][y] = value.y;
|
||||||
|
spd_intermediate_b[x][y] = value.z;
|
||||||
|
spd_intermediate_a[x][y] = value.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_load_intermediate(x: u32, y: u32) -> vec4f {
|
||||||
|
return vec4(spd_intermediate_r[x][y], spd_intermediate_g[x][y], spd_intermediate_b[x][y], spd_intermediate_a[x][y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_reduce_intermediate(i0: vec2u, i1: vec2u, i2: vec2u, i3: vec2u) -> vec4f {
|
||||||
|
let v0 = spd_load_intermediate(i0.x, i0.y);
|
||||||
|
let v1 = spd_load_intermediate(i1.x, i1.y);
|
||||||
|
let v2 = spd_load_intermediate(i2.x, i2.y);
|
||||||
|
let v3 = spd_load_intermediate(i3.x, i3.y);
|
||||||
|
return spd_reduce_4(v0, v1, v2, v3);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_reduce_load_4(i0: vec2u, i1: vec2u, i2: vec2u, i3: vec2u, slice: u32) -> vec4f {
|
||||||
|
let v0 = textureLoad(mip_6, i0, slice);
|
||||||
|
let v1 = textureLoad(mip_6, i1, slice);
|
||||||
|
let v2 = textureLoad(mip_6, i2, slice);
|
||||||
|
let v3 = textureLoad(mip_6, i3, slice);
|
||||||
|
return spd_reduce_4(v0, v1, v2, v3);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spd_reduce_4(v0: vec4f, v1: vec4f, v2: vec4f, v3: vec4f) -> vec4f {
|
||||||
|
return (v0 + v1 + v2 + v3) * 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SUBGROUP_SUPPORT
|
||||||
|
fn spd_reduce_quad(v: vec4f, subgroup_invocation_id: u32) -> vec4f {
|
||||||
|
let quad = subgroup_invocation_id & (~0x3u);
|
||||||
|
let v0 = v;
|
||||||
|
let v1 = subgroupBroadcast(v, quad | 1u);
|
||||||
|
let v2 = subgroupBroadcast(v, quad | 2u);
|
||||||
|
let v3 = subgroupBroadcast(v, quad | 3u);
|
||||||
|
return spd_reduce_4(v0, v1, v2, v3);
|
||||||
|
|
||||||
|
// TODO: Use subgroup quad operations once wgpu supports them
|
||||||
|
// let v0 = v;
|
||||||
|
// let v1 = quadSwapX(v);
|
||||||
|
// let v2 = quadSwapY(v);
|
||||||
|
// let v3 = quadSwapDiagonal(v);
|
||||||
|
// return spd_reduce_4(v0, v1, v2, v3);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fn srgb_from_linear(value: f32) -> f32 {
|
||||||
|
let j = vec3(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
|
||||||
|
let k = vec2(1.055, -0.055);
|
||||||
|
return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y);
|
||||||
|
}
|
||||||
@ -37,6 +37,8 @@ enum ReflectionMode {
|
|||||||
// Both a world environment map and a reflection probe are present. The
|
// Both a world environment map and a reflection probe are present. The
|
||||||
// reflection probe is shown in the sphere.
|
// reflection probe is shown in the sphere.
|
||||||
ReflectionProbe = 2,
|
ReflectionProbe = 2,
|
||||||
|
// A prefiltered environment map is shown.
|
||||||
|
PrefilteredEnvironmentMap = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The various reflection maps.
|
// The various reflection maps.
|
||||||
@ -53,6 +55,9 @@ struct Cubemaps {
|
|||||||
// The specular cubemap that reflects both the world and the cubes.
|
// The specular cubemap that reflects both the world and the cubes.
|
||||||
specular_reflection_probe: Handle<Image>,
|
specular_reflection_probe: Handle<Image>,
|
||||||
|
|
||||||
|
// Unfiltered environment map
|
||||||
|
unfiltered_environment_map: Handle<Image>,
|
||||||
|
|
||||||
// The skybox cubemap image. This is almost the same as
|
// The skybox cubemap image. This is almost the same as
|
||||||
// `specular_environment_map`.
|
// `specular_environment_map`.
|
||||||
skybox: Handle<Image>,
|
skybox: Handle<Image>,
|
||||||
@ -146,6 +151,21 @@ fn spawn_reflection_probe(commands: &mut Commands, cubemaps: &Cubemaps) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn spawn_prefiltered_environment_map(commands: &mut Commands, cubemaps: &Cubemaps) {
|
||||||
|
println!("spawn_prefiltered_environment_map");
|
||||||
|
commands.spawn((
|
||||||
|
LightProbe,
|
||||||
|
FilteredEnvironmentMapLight {
|
||||||
|
environment_map: cubemaps.unfiltered_environment_map.clone(),
|
||||||
|
intensity: 5000.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
// 2.0 because the sphere's radius is 1.0 and we want to fully enclose it.
|
||||||
|
Transform::from_scale(Vec3::splat(2.0)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Spawns the help text.
|
// Spawns the help text.
|
||||||
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
|
||||||
// Create the text.
|
// Create the text.
|
||||||
@ -196,7 +216,7 @@ fn change_reflection_type(
|
|||||||
|
|
||||||
// Switch reflection mode.
|
// Switch reflection mode.
|
||||||
app_status.reflection_mode =
|
app_status.reflection_mode =
|
||||||
ReflectionMode::try_from((app_status.reflection_mode as u32 + 1) % 3).unwrap();
|
ReflectionMode::try_from((app_status.reflection_mode as u32 + 1) % 4).unwrap();
|
||||||
|
|
||||||
// Add or remove the light probe.
|
// Add or remove the light probe.
|
||||||
for light_probe in light_probe_query.iter() {
|
for light_probe in light_probe_query.iter() {
|
||||||
@ -205,6 +225,7 @@ fn change_reflection_type(
|
|||||||
match app_status.reflection_mode {
|
match app_status.reflection_mode {
|
||||||
ReflectionMode::None | ReflectionMode::EnvironmentMap => {}
|
ReflectionMode::None | ReflectionMode::EnvironmentMap => {}
|
||||||
ReflectionMode::ReflectionProbe => spawn_reflection_probe(&mut commands, &cubemaps),
|
ReflectionMode::ReflectionProbe => spawn_reflection_probe(&mut commands, &cubemaps),
|
||||||
|
ReflectionMode::PrefilteredEnvironmentMap => spawn_prefiltered_environment_map(&mut commands, &cubemaps),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add or remove the environment map from the camera.
|
// Add or remove the environment map from the camera.
|
||||||
@ -213,7 +234,7 @@ fn change_reflection_type(
|
|||||||
ReflectionMode::None => {
|
ReflectionMode::None => {
|
||||||
commands.entity(camera).remove::<EnvironmentMapLight>();
|
commands.entity(camera).remove::<EnvironmentMapLight>();
|
||||||
}
|
}
|
||||||
ReflectionMode::EnvironmentMap | ReflectionMode::ReflectionProbe => {
|
ReflectionMode::EnvironmentMap | ReflectionMode::ReflectionProbe | ReflectionMode::PrefilteredEnvironmentMap => {
|
||||||
commands
|
commands
|
||||||
.entity(camera)
|
.entity(camera)
|
||||||
.insert(create_camera_environment_map_light(&cubemaps));
|
.insert(create_camera_environment_map_light(&cubemaps));
|
||||||
@ -244,6 +265,7 @@ impl TryFrom<u32> for ReflectionMode {
|
|||||||
0 => Ok(ReflectionMode::None),
|
0 => Ok(ReflectionMode::None),
|
||||||
1 => Ok(ReflectionMode::EnvironmentMap),
|
1 => Ok(ReflectionMode::EnvironmentMap),
|
||||||
2 => Ok(ReflectionMode::ReflectionProbe),
|
2 => Ok(ReflectionMode::ReflectionProbe),
|
||||||
|
3 => Ok(ReflectionMode::PrefilteredEnvironmentMap),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -255,6 +277,7 @@ impl Display for ReflectionMode {
|
|||||||
ReflectionMode::None => "No reflections",
|
ReflectionMode::None => "No reflections",
|
||||||
ReflectionMode::EnvironmentMap => "Environment map",
|
ReflectionMode::EnvironmentMap => "Environment map",
|
||||||
ReflectionMode::ReflectionProbe => "Reflection probe",
|
ReflectionMode::ReflectionProbe => "Reflection probe",
|
||||||
|
ReflectionMode::PrefilteredEnvironmentMap => "Prefiltered environment map",
|
||||||
};
|
};
|
||||||
formatter.write_str(text)
|
formatter.write_str(text)
|
||||||
}
|
}
|
||||||
@ -321,6 +344,7 @@ impl FromWorld for Cubemaps {
|
|||||||
specular_reflection_probe: world
|
specular_reflection_probe: world
|
||||||
.load_asset("environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2"),
|
.load_asset("environment_maps/cubes_reflection_probe_specular_rgb9e5_zstd.ktx2"),
|
||||||
specular_environment_map: specular_map.clone(),
|
specular_environment_map: specular_map.clone(),
|
||||||
|
unfiltered_environment_map: world.load_asset("environment_maps/ballawley_park_1k.ktx2"),
|
||||||
skybox: specular_map,
|
skybox: specular_map,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -329,8 +353,8 @@ impl FromWorld for Cubemaps {
|
|||||||
impl Default for AppStatus {
|
impl Default for AppStatus {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
reflection_mode: ReflectionMode::ReflectionProbe,
|
reflection_mode: ReflectionMode::PrefilteredEnvironmentMap,
|
||||||
rotating: true,
|
rotating: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user