Remove hardcoded uniform directions add whitepoint param

This commit is contained in:
Máté Homolya 2025-05-08 11:55:13 -07:00
parent 937c738591
commit b9374ee6f3
No known key found for this signature in database
2 changed files with 64 additions and 124 deletions

View File

@ -1,4 +1,4 @@
#import bevy_render::maths::{PI, PI_2};
#import bevy_render::maths::{PI, PI_2, fast_sqrt};
#import bevy_pbr::lighting::perceptualRoughnessToRoughness;
struct FilteringConstants {
@ -6,6 +6,7 @@ struct FilteringConstants {
sample_count: u32,
roughness: f32,
blue_noise_size: vec2f,
white_point: f32,
}
@group(0) @binding(0) var input_texture: texture_2d_array<f32>;
@ -15,90 +16,11 @@ struct FilteringConstants {
@group(0) @binding(4) var blue_noise_texture: texture_2d<f32>;
// Tonemapping functions to reduce fireflies
const white_point: f32 = 1.0;
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(white_point));
return color / (color + vec3(constants.white_point));
}
fn reverse_tonemap(color: vec3f) -> vec3f {
return white_point * 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);
return constants.white_point * color / (vec3(1.0) - color);
}
// Convert UV and face index to direction vector
@ -197,7 +119,6 @@ fn hammersley_2d(i: u32, n: u32) -> vec2f {
// Blue noise randomization
fn sample_noise(pixel_coords: vec2u) -> vec4f {
// 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;
let uv = vec2f(noise_coords) / constants.blue_noise_size;
@ -222,8 +143,8 @@ fn importance_sample_ggx(xi: vec2f, roughness: f32, normal: vec3f) -> vec3f {
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);
let cos_theta = fast_sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y));
let sin_theta = fast_sqrt(1.0 - cos_theta * cos_theta);
// Convert to cartesian
let h = vec3f(
@ -351,6 +272,30 @@ fn generate_radiance_map(@builtin(global_invocation_id) global_id: vec3u) {
textureStore(output_texture, coords, face, vec4f(radiance, 1.0));
}
// Calculate spherical coordinates using spiral pattern
// and golden angle to get a uniform distribution
fn uniform_sample_sphere(i: u32, normal: vec3f) -> vec3f {
// Get stratified sample index
let strat_i = i % constants.sample_count;
let golden_angle = 2.4;
let full_sphere = f32(constants.sample_count) * 2.0;
let z = 1.0 - (2.0 * f32(strat_i) + 1.0) / full_sphere;
let r = fast_sqrt(1.0 - z * z);
let phi = f32(strat_i) * golden_angle;
// Create the direction vector
let dir_uniform = vec3f(
r * cos(phi),
r * sin(phi),
z
);
let tangent_frame = calculate_tangent_frame(normal);
return normalize(tangent_frame * dir_uniform);
}
@compute
@workgroup_size(8, 8, 1)
fn generate_irradiance_map(@builtin(global_invocation_id) global_id: vec3u) {
@ -368,54 +313,41 @@ fn generate_irradiance_map(@builtin(global_invocation_id) global_id: vec3u) {
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);
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);
// Use uniform sampling on a hemisphere
for (var i = 0u; i < constants.sample_count; i++) {
// Get a uniform direction on unit sphere
var sample_dir = uniform_sample_sphere(i, normal);
// Ensure the direction is in the hemisphere defined by the normal
let NoL = dot(normal, dir);
// Calculate the cosine weight (N·L)
let weight = max(dot(normal, sample_dir), 0.0);
// Flip the direction if it's in the wrong hemisphere
if (NoL < 0.0) {
dir = -dir;
// Skip samples below horizon or at grazing angles
if (weight <= 0.001) {
continue;
}
// Recalculate NoL after possible flipping
let weight = max(dot(normal, dir), 0.0);
// Sample environment with level 0 (no mip)
var sample_color = sample_environment(sample_dir, 0.0).rgb;
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;
}
// Apply tonemapping to reduce fireflies
sample_color = tonemap(sample_color);
// Accumulate the contribution
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;
}
// Normalize by total weight
irradiance = irradiance / total_weight;
// Scale by PI to account for the Lambert BRDF normalization factor
irradiance *= PI;
// Reverse tonemap to restore HDR range
irradiance = reverse_tonemap(irradiance);
// Write result to output texture
textureStore(output_texture, coords, face, vec4f(irradiance, 1.0));

View File

@ -325,6 +325,7 @@ pub struct GeneratedEnvironmentMapLight {
pub intensity: f32,
pub rotation: Quat,
pub affects_lightmapped_mesh_diffuse: bool,
pub white_point: f32,
}
impl Default for GeneratedEnvironmentMapLight {
@ -334,6 +335,7 @@ impl Default for GeneratedEnvironmentMapLight {
intensity: 0.0,
rotation: Quat::IDENTITY,
affects_lightmapped_mesh_diffuse: true,
white_point: 2.0,
}
}
}
@ -372,6 +374,7 @@ pub fn extract_generator_entities(
intensity: filtered_env_map.intensity,
rotation: filtered_env_map.rotation,
affects_lightmapped_mesh_diffuse: filtered_env_map.affects_lightmapped_mesh_diffuse,
white_point: filtered_env_map.white_point,
};
commands
.get_entity(entity)
@ -389,6 +392,7 @@ pub struct RenderEnvironmentMap {
pub intensity: f32,
pub rotation: Quat,
pub affects_lightmapped_mesh_diffuse: bool,
pub white_point: f32,
}
#[derive(Component)]
@ -448,6 +452,7 @@ pub struct FilteringConstants {
sample_count: u32,
roughness: f32,
blue_noise_size: Vec2,
white_point: f32,
}
/// Stores bind groups for the environment map generation pipelines
@ -586,6 +591,7 @@ pub fn prepare_generator_bind_groups(
vector2_uniform.size.width as f32,
vector2_uniform.size.height as f32,
),
white_point: env_map_light.white_point,
};
let mut radiance_constants_buffer = UniformBuffer::from(radiance_constants);
@ -614,12 +620,14 @@ pub fn prepare_generator_bind_groups(
// Create irradiance bind group
let irradiance_constants = FilteringConstants {
mip_level: 0.0,
sample_count: 64,
// 32 phi, 32 theta = 1024 samples total
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,
),
white_point: env_map_light.white_point,
};
let mut irradiance_constants_buffer = UniformBuffer::from(irradiance_constants);