From 9e28dd327740b50db0b97e8b51a5c064e2a3dfb4 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:51:54 -0500 Subject: [PATCH 01/11] Solari fixes --- crates/bevy_solari/src/realtime/restir_gi.wgsl | 2 +- crates/bevy_solari/src/realtime/world_cache_update.wgsl | 2 +- crates/bevy_solari/src/scene/brdf.wgsl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_solari/src/realtime/restir_gi.wgsl b/crates/bevy_solari/src/realtime/restir_gi.wgsl index 4fcf537a496b4..7d3a60ac9e6d3 100644 --- a/crates/bevy_solari/src/realtime/restir_gi.wgsl +++ b/crates/bevy_solari/src/realtime/restir_gi.wgsl @@ -274,7 +274,7 @@ fn merge_reservoirs( ); // Don't merge samples with huge jacobians, as it explodes the variance - if canonical_target_function_other_sample_jacobian > 2.0 { + if canonical_target_function_other_sample_jacobian > 1.2 { return ReservoirMergeResult(canonical_reservoir, canonical_sample_radiance); } diff --git a/crates/bevy_solari/src/realtime/world_cache_update.wgsl b/crates/bevy_solari/src/realtime/world_cache_update.wgsl index 44a387c8fce71..87638f9d8dc03 100644 --- a/crates/bevy_solari/src/realtime/world_cache_update.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_update.wgsl @@ -19,7 +19,7 @@ struct PushConstants { frame_index: u32, reset: u32 } var constants: PushConstants; -const DIRECT_LIGHT_SAMPLE_COUNT: u32 = 32u; +const DIRECT_LIGHT_SAMPLE_COUNT: u32 = 8u; @compute @workgroup_size(1024, 1, 1) fn sample_radiance(@builtin(workgroup_id) workgroup_id: vec3, @builtin(global_invocation_id) active_cell_id: vec3) { diff --git a/crates/bevy_solari/src/scene/brdf.wgsl b/crates/bevy_solari/src/scene/brdf.wgsl index dd78e83c7ff88..b6fae4b6b13bf 100644 --- a/crates/bevy_solari/src/scene/brdf.wgsl +++ b/crates/bevy_solari/src/scene/brdf.wgsl @@ -50,7 +50,7 @@ fn evaluate_specular_brdf( let F_ab = F_AB(perceptual_roughness, NdotV); let D = D_GGX(roughness, NdotH); - let Vs = V_SmithGGXCorrelated(roughness, NdotV, NdotL); + let Vs = saturate(V_SmithGGXCorrelated(roughness, NdotV, NdotL)); let F = fresnel(F0, LdotH); return specular_multiscatter(D, Vs, F, F0, F_ab, 1.0); } From 0dfa41774e221ac751874d3da8fb5b3546f6491d Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:55:04 -0500 Subject: [PATCH 02/11] Properly fix NaNs --- crates/bevy_solari/src/realtime/gbuffer_utils.wgsl | 2 +- crates/bevy_solari/src/realtime/specular_gi.wgsl | 2 +- crates/bevy_solari/src/scene/brdf.wgsl | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl b/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl index a9d513f77e8e1..43e0a314ba020 100644 --- a/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl +++ b/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl @@ -22,7 +22,7 @@ fn gpixel_resolve(gpixel: vec4, depth: f32, pixel_id: vec2, view_size: let roughness = clamp(perceptual_roughness * perceptual_roughness, 0.001, 1.0); let props = unpack4x8unorm(gpixel.b); let reflectance = vec3(props.r); - let metallic = props.g; + let metallic = saturate(props.g); // TODO: Not sure why saturate is needed here to prevent NaNs let emissive = rgb9e5_to_vec3_(gpixel.g); let material = ResolvedMaterial(base_color, emissive, reflectance, perceptual_roughness, roughness, metallic); diff --git a/crates/bevy_solari/src/realtime/specular_gi.wgsl b/crates/bevy_solari/src/realtime/specular_gi.wgsl index 9900b190db9fc..4ddeaff08b1b7 100644 --- a/crates/bevy_solari/src/realtime/specular_gi.wgsl +++ b/crates/bevy_solari/src/realtime/specular_gi.wgsl @@ -52,7 +52,7 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3) { } let brdf = evaluate_specular_brdf(surface.world_normal, wo, wi, surface.material.base_color, surface.material.metallic, - surface.material.reflectance, surface.material.perceptual_roughness, surface.material.roughness); + surface.material.reflectance, surface.material.perceptual_roughness); let cos_theta = saturate(dot(wi, surface.world_normal)); radiance *= brdf * cos_theta * view.exposure; diff --git a/crates/bevy_solari/src/scene/brdf.wgsl b/crates/bevy_solari/src/scene/brdf.wgsl index b6fae4b6b13bf..51ee63c782dd4 100644 --- a/crates/bevy_solari/src/scene/brdf.wgsl +++ b/crates/bevy_solari/src/scene/brdf.wgsl @@ -20,7 +20,6 @@ fn evaluate_brdf( material.metallic, material.reflectance, material.perceptual_roughness, - material.roughness, ); return diffuse_brdf + specular_brdf; } @@ -38,8 +37,11 @@ fn evaluate_specular_brdf( metallic: f32, reflectance: vec3, perceptual_roughness: f32, - roughness: f32, ) -> vec3 { + // Clamp roughness to prevent NaNs + let perceptual_roughness_clamped = clamp(perceptual_roughness, 0.0316227766, 1.0); + let roughness = perceptual_roughness_clamped * perceptual_roughness_clamped; + let H = normalize(L + V); let NdotL = saturate(dot(N, L)); let NdotH = saturate(dot(N, H)); @@ -47,10 +49,10 @@ fn evaluate_specular_brdf( let NdotV = max(dot(N, V), 0.0001); let F0 = calculate_F0(base_color, metallic, reflectance); - let F_ab = F_AB(perceptual_roughness, NdotV); + let F_ab = F_AB(perceptual_roughness_clamped, NdotV); let D = D_GGX(roughness, NdotH); - let Vs = saturate(V_SmithGGXCorrelated(roughness, NdotV, NdotL)); + let Vs = V_SmithGGXCorrelated(roughness, NdotV, NdotL); let F = fresnel(F0, LdotH); return specular_multiscatter(D, Vs, F, F0, F_ab, 1.0); } From 4a7376dc2b24bf96ed739c3d1e8b6b57adfd806f Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:14:33 -0500 Subject: [PATCH 03/11] Revert world cache changes --- DLSS | 1 + crates/bevy_solari/src/realtime/world_cache_update.wgsl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 DLSS diff --git a/DLSS b/DLSS new file mode 160000 index 0000000000000..9a6b48a79d5ae --- /dev/null +++ b/DLSS @@ -0,0 +1 @@ +Subproject commit 9a6b48a79d5ae41bf1481d0c83d73859ec481bd2 diff --git a/crates/bevy_solari/src/realtime/world_cache_update.wgsl b/crates/bevy_solari/src/realtime/world_cache_update.wgsl index 87638f9d8dc03..44a387c8fce71 100644 --- a/crates/bevy_solari/src/realtime/world_cache_update.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_update.wgsl @@ -19,7 +19,7 @@ struct PushConstants { frame_index: u32, reset: u32 } var constants: PushConstants; -const DIRECT_LIGHT_SAMPLE_COUNT: u32 = 8u; +const DIRECT_LIGHT_SAMPLE_COUNT: u32 = 32u; @compute @workgroup_size(1024, 1, 1) fn sample_radiance(@builtin(workgroup_id) workgroup_id: vec3, @builtin(global_invocation_id) active_cell_id: vec3) { From d88fa9ddb4d65df2aa894ff7c03df2910bc3eafd Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:15:56 -0500 Subject: [PATCH 04/11] Remove subproject --- DLSS | 1 - 1 file changed, 1 deletion(-) delete mode 160000 DLSS diff --git a/DLSS b/DLSS deleted file mode 160000 index 9a6b48a79d5ae..0000000000000 --- a/DLSS +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9a6b48a79d5ae41bf1481d0c83d73859ec481bd2 From bb9e690067f0067403b940bc3b0eb39ce01bde26 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:03:20 -0500 Subject: [PATCH 05/11] Move around roughness clamp --- crates/bevy_solari/src/pathtracer/pathtracer.wgsl | 4 ++-- crates/bevy_solari/src/realtime/gbuffer_utils.wgsl | 5 +++-- crates/bevy_solari/src/realtime/specular_gi.wgsl | 2 +- crates/bevy_solari/src/scene/brdf.wgsl | 8 +++----- .../bevy_solari/src/scene/raytracing_scene_bindings.wgsl | 5 ++++- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/bevy_solari/src/pathtracer/pathtracer.wgsl b/crates/bevy_solari/src/pathtracer/pathtracer.wgsl index 68cb45e207ffb..77fc834b2f5a5 100644 --- a/crates/bevy_solari/src/pathtracer/pathtracer.wgsl +++ b/crates/bevy_solari/src/pathtracer/pathtracer.wgsl @@ -104,7 +104,7 @@ fn importance_sample_next_bounce(wo: vec3, ray_hit: ResolvedRayHitFull, rng if is_perfectly_specular { return NextBounce(reflect(-wo, ray_hit.world_normal), 1.0, true); } - let diffuse_weight = mix(mix(0.4f, 0.9f, ray_hit.material.perceptual_roughness), 0.f, ray_hit.material.metallic); + let diffuse_weight = mix(mix(0.4, 0.9, ray_hit.material.perceptual_roughness), 0.0, ray_hit.material.metallic); let specular_weight = 1.0 - diffuse_weight; let TBN = calculate_tbn_mikktspace(ray_hit.world_normal, ray_hit.world_tangent); @@ -133,7 +133,7 @@ fn importance_sample_next_bounce(wo: vec3, ray_hit: ResolvedRayHitFull, rng } fn brdf_pdf(wo: vec3, wi: vec3, ray_hit: ResolvedRayHitFull) -> f32 { - let diffuse_weight = mix(mix(0.4f, 0.9f, ray_hit.material.roughness), 0.f, ray_hit.material.metallic); + let diffuse_weight = mix(mix(0.4, 0.9, ray_hit.material.roughness), 0.0, ray_hit.material.metallic); let specular_weight = 1.0 - diffuse_weight; let TBN = calculate_tbn_mikktspace(ray_hit.world_normal, ray_hit.world_tangent); diff --git a/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl b/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl index 43e0a314ba020..21028a4b5cb32 100644 --- a/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl +++ b/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl @@ -18,8 +18,9 @@ fn gpixel_resolve(gpixel: vec4, depth: f32, pixel_id: vec2, view_size: let base_rough = unpack4x8unorm(gpixel.r); let base_color = pow(base_rough.rgb, vec3(2.2)); - let perceptual_roughness = base_rough.a; - let roughness = clamp(perceptual_roughness * perceptual_roughness, 0.001, 1.0); + // Clamp roughness to prevent NaNs + let perceptual_roughness = clamp(base_rough.a, 0.0316227766, 1.0); + let roughness = perceptual_roughness * perceptual_roughness; let props = unpack4x8unorm(gpixel.b); let reflectance = vec3(props.r); let metallic = saturate(props.g); // TODO: Not sure why saturate is needed here to prevent NaNs diff --git a/crates/bevy_solari/src/realtime/specular_gi.wgsl b/crates/bevy_solari/src/realtime/specular_gi.wgsl index 4ddeaff08b1b7..9900b190db9fc 100644 --- a/crates/bevy_solari/src/realtime/specular_gi.wgsl +++ b/crates/bevy_solari/src/realtime/specular_gi.wgsl @@ -52,7 +52,7 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3) { } let brdf = evaluate_specular_brdf(surface.world_normal, wo, wi, surface.material.base_color, surface.material.metallic, - surface.material.reflectance, surface.material.perceptual_roughness); + surface.material.reflectance, surface.material.perceptual_roughness, surface.material.roughness); let cos_theta = saturate(dot(wi, surface.world_normal)); radiance *= brdf * cos_theta * view.exposure; diff --git a/crates/bevy_solari/src/scene/brdf.wgsl b/crates/bevy_solari/src/scene/brdf.wgsl index 51ee63c782dd4..dd78e83c7ff88 100644 --- a/crates/bevy_solari/src/scene/brdf.wgsl +++ b/crates/bevy_solari/src/scene/brdf.wgsl @@ -20,6 +20,7 @@ fn evaluate_brdf( material.metallic, material.reflectance, material.perceptual_roughness, + material.roughness, ); return diffuse_brdf + specular_brdf; } @@ -37,11 +38,8 @@ fn evaluate_specular_brdf( metallic: f32, reflectance: vec3, perceptual_roughness: f32, + roughness: f32, ) -> vec3 { - // Clamp roughness to prevent NaNs - let perceptual_roughness_clamped = clamp(perceptual_roughness, 0.0316227766, 1.0); - let roughness = perceptual_roughness_clamped * perceptual_roughness_clamped; - let H = normalize(L + V); let NdotL = saturate(dot(N, L)); let NdotH = saturate(dot(N, H)); @@ -49,7 +47,7 @@ fn evaluate_specular_brdf( let NdotV = max(dot(N, V), 0.0001); let F0 = calculate_F0(base_color, metallic, reflectance); - let F_ab = F_AB(perceptual_roughness_clamped, NdotV); + let F_ab = F_AB(perceptual_roughness, NdotV); let D = D_GGX(roughness, NdotH); let Vs = V_SmithGGXCorrelated(roughness, NdotV, NdotL); diff --git a/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl b/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl index 9e346bbf2d783..6983483e3c261 100644 --- a/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl +++ b/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl @@ -142,7 +142,10 @@ fn resolve_material(material: Material, uv: vec2) -> ResolvedMaterial { m.perceptual_roughness *= metallic_roughness.g; m.metallic *= metallic_roughness.b; } - m.roughness = clamp(m.perceptual_roughness * m.perceptual_roughness, 0.001, 1.0); + + // Clamp roughness to prevent NaNs + m.perceptual_roughness = clamp(m.perceptual_roughness, 0.0316227766, 1.0); + m.roughness = m.perceptual_roughness * m.perceptual_roughness; return m; } From 81b53e04461dab2f5d91df92ee34250cdf292f62 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:05:33 -0500 Subject: [PATCH 06/11] Add comment --- crates/bevy_solari/src/realtime/gbuffer_utils.wgsl | 2 +- crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl b/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl index 21028a4b5cb32..baf1556806b6e 100644 --- a/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl +++ b/crates/bevy_solari/src/realtime/gbuffer_utils.wgsl @@ -19,7 +19,7 @@ fn gpixel_resolve(gpixel: vec4, depth: f32, pixel_id: vec2, view_size: let base_rough = unpack4x8unorm(gpixel.r); let base_color = pow(base_rough.rgb, vec3(2.2)); // Clamp roughness to prevent NaNs - let perceptual_roughness = clamp(base_rough.a, 0.0316227766, 1.0); + let perceptual_roughness = clamp(base_rough.a, 0.0316227766, 1.0); // Clamp roughness to 0.001 let roughness = perceptual_roughness * perceptual_roughness; let props = unpack4x8unorm(gpixel.b); let reflectance = vec3(props.r); diff --git a/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl b/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl index 6983483e3c261..8b1e875fc5423 100644 --- a/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl +++ b/crates/bevy_solari/src/scene/raytracing_scene_bindings.wgsl @@ -144,7 +144,7 @@ fn resolve_material(material: Material, uv: vec2) -> ResolvedMaterial { } // Clamp roughness to prevent NaNs - m.perceptual_roughness = clamp(m.perceptual_roughness, 0.0316227766, 1.0); + m.perceptual_roughness = clamp(m.perceptual_roughness, 0.0316227766, 1.0); // Clamp roughness to 0.001 m.roughness = m.perceptual_roughness * m.perceptual_roughness; return m; From 6a13dbe562173f2bd576c1bb3575578e619106e6 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:20:43 -0500 Subject: [PATCH 07/11] Solari: Prevent world cache cells from keeping each other alive infinitely --- crates/bevy_solari/src/realtime/node.rs | 2 +- .../bevy_solari/src/realtime/restir_gi.wgsl | 4 ++-- .../bevy_solari/src/realtime/specular_gi.wgsl | 6 +++--- .../src/realtime/world_cache_query.wgsl | 19 +++++++++++++++---- .../src/realtime/world_cache_update.wgsl | 11 ++++++----- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index 27fa95cd298bd..1bd392fa439e6 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -498,7 +498,7 @@ impl FromWorld for SolariLightingNode { "sample_radiance", load_embedded_asset!(world, "world_cache_update.wgsl"), None, - vec![], + vec!["WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME".into()], ), blend_new_world_cache_samples_pipeline: create_pipeline( "solari_lighting_blend_new_world_cache_samples_pipeline", diff --git a/crates/bevy_solari/src/realtime/restir_gi.wgsl b/crates/bevy_solari/src/realtime/restir_gi.wgsl index 7d3a60ac9e6d3..320772d65edbb 100644 --- a/crates/bevy_solari/src/realtime/restir_gi.wgsl +++ b/crates/bevy_solari/src/realtime/restir_gi.wgsl @@ -9,7 +9,7 @@ #import bevy_solari::gbuffer_utils::{gpixel_resolve, pixel_dissimilar, permute_pixel} #import bevy_solari::sampling::{sample_random_light, trace_point_visibility} #import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, RAY_T_MIN, RAY_T_MAX} -#import bevy_solari::world_cache::query_world_cache +#import bevy_solari::world_cache::{query_world_cache, WORLD_CACHE_CELL_LIFETIME} @group(1) @binding(0) var view_output: texture_storage_2d; @group(1) @binding(5) var gi_reservoirs_a: array; @@ -105,7 +105,7 @@ fn generate_initial_reservoir(world_position: vec3, world_normal: vec3 reservoir.radiance = direct_lighting.radiance; reservoir.unbiased_contribution_weight = direct_lighting.inverse_pdf * uniform_hemisphere_inverse_pdf(); #else - reservoir.radiance = query_world_cache(sample_point.world_position, sample_point.geometric_world_normal, view.world_position, rng); + reservoir.radiance = query_world_cache(sample_point.world_position, sample_point.geometric_world_normal, view.world_position, WORLD_CACHE_CELL_LIFETIME, rng); reservoir.unbiased_contribution_weight = uniform_hemisphere_inverse_pdf(); #endif diff --git a/crates/bevy_solari/src/realtime/specular_gi.wgsl b/crates/bevy_solari/src/realtime/specular_gi.wgsl index 9900b190db9fc..335c5588271de 100644 --- a/crates/bevy_solari/src/realtime/specular_gi.wgsl +++ b/crates/bevy_solari/src/realtime/specular_gi.wgsl @@ -5,7 +5,7 @@ #import bevy_solari::gbuffer_utils::gpixel_resolve #import bevy_solari::sampling::{sample_ggx_vndf, ggx_vndf_pdf} #import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, RAY_T_MIN, RAY_T_MAX} -#import bevy_solari::world_cache::query_world_cache +#import bevy_solari::world_cache::{query_world_cache, WORLD_CACHE_CELL_LIFETIME} @group(1) @binding(0) var view_output: texture_storage_2d; @group(1) @binding(5) var gi_reservoirs_a: array; @@ -61,7 +61,7 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3) { textureStore(view_output, global_id.xy, pixel_color); #ifdef VISUALIZE_WORLD_CACHE - textureStore(view_output, global_id.xy, vec4(query_world_cache(surface.world_position, surface.world_normal, view.world_position, &rng) * view.exposure, 1.0)); + textureStore(view_output, global_id.xy, vec4(query_world_cache(surface.world_position, surface.world_normal, view.world_position, WORLD_CACHE_CELL_LIFETIME, &rng) * view.exposure, 1.0)); #endif } @@ -80,7 +80,7 @@ fn trace_glossy_path(initial_ray_origin: vec3, initial_wi: vec3, rng: // Add world cache contribution let diffuse_brdf = ray_hit.material.base_color / PI; - radiance += throughput * diffuse_brdf * query_world_cache(ray_hit.world_position, ray_hit.geometric_world_normal, view.world_position, rng); + radiance += throughput * diffuse_brdf * query_world_cache(ray_hit.world_position, ray_hit.geometric_world_normal, view.world_position, WORLD_CACHE_CELL_LIFETIME, rng); // Surface is very rough, terminate path in the world cache if ray_hit.material.roughness > 0.1 && i != 0u { break; } diff --git a/crates/bevy_solari/src/realtime/world_cache_query.wgsl b/crates/bevy_solari/src/realtime/world_cache_query.wgsl index dac506836badc..b86cc4702303f 100644 --- a/crates/bevy_solari/src/realtime/world_cache_query.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_query.wgsl @@ -6,7 +6,7 @@ /// How responsive the world cache is to changes in lighting (higher is less responsive, lower is more responsive) const WORLD_CACHE_MAX_TEMPORAL_SAMPLES: f32 = 10.0; /// Maximum amount of frames a cell can live for without being queried -const WORLD_CACHE_CELL_LIFETIME: u32 = 4u; +const WORLD_CACHE_CELL_LIFETIME: u32 = 30u; /// Maximum amount of attempts to find a cache entry after a hash collision const WORLD_CACHE_MAX_SEARCH_STEPS: u32 = 3u; @@ -15,6 +15,9 @@ const WORLD_CACHE_POSITION_BASE_CELL_SIZE: f32 = 0.25; /// How fast the world cache transitions between LODs as a function of distance to the camera const WORLD_CACHE_POSITION_LOD_SCALE: f32 = 30.0; +/// How many direct light samples each cell takes when updating each frame +const WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT: u32 = 32u; + /// Marker value for an empty cell const WORLD_CACHE_EMPTY_CELL: u32 = 0u; @@ -40,7 +43,7 @@ struct WorldCacheGeometryData { @group(1) @binding(22) var world_cache_active_cells_count: u32; #ifndef WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER -fn query_world_cache(world_position: vec3, world_normal: vec3, view_position: vec3, rng: ptr) -> vec3 { +fn query_world_cache(world_position: vec3, world_normal: vec3, view_position: vec3, cell_lifetime: u32, rng: ptr) -> vec3 { let cell_size = get_cell_size(world_position, view_position); // https://tomclabault.github.io/blog/2025/regir, jitter_world_position_tangent_plane @@ -57,11 +60,19 @@ fn query_world_cache(world_position: vec3, world_normal: vec3, view_po let existing_checksum = atomicCompareExchangeWeak(&world_cache_checksums[key], WORLD_CACHE_EMPTY_CELL, checksum).old_value; if existing_checksum == checksum { // Cache entry already exists - get radiance and reset cell lifetime - atomicStore(&world_cache_life[key], WORLD_CACHE_CELL_LIFETIME); +#ifndef WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME + atomicStore(&world_cache_life[key], cell_lifetime); +#elseif + atomicMax(&world_cache_life[key], cell_lifetime); +#endif return world_cache_radiance[key].rgb; } else if existing_checksum == WORLD_CACHE_EMPTY_CELL { // Cell is empty - reset cell lifetime so that it starts getting updated next frame - atomicStore(&world_cache_life[key], WORLD_CACHE_CELL_LIFETIME); +#ifndef WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME + atomicStore(&world_cache_life[key], cell_lifetime); +#elseif + atomicMax(&world_cache_life[key], cell_lifetime); +#endif world_cache_geometry_data[key].world_position = jittered_position; world_cache_geometry_data[key].world_normal = world_normal; return vec3(0.0); diff --git a/crates/bevy_solari/src/realtime/world_cache_update.wgsl b/crates/bevy_solari/src/realtime/world_cache_update.wgsl index 44a387c8fce71..25ecbf17e7374 100644 --- a/crates/bevy_solari/src/realtime/world_cache_update.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_update.wgsl @@ -6,9 +6,11 @@ #import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, RAY_T_MIN, RAY_T_MAX} #import bevy_solari::world_cache::{ WORLD_CACHE_MAX_TEMPORAL_SAMPLES, +WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT, query_world_cache, world_cache_active_cells_count, world_cache_active_cell_indices, +world_cache_life, world_cache_geometry_data, world_cache_radiance, world_cache_active_cells_new_radiance, @@ -19,8 +21,6 @@ struct PushConstants { frame_index: u32, reset: u32 } var constants: PushConstants; -const DIRECT_LIGHT_SAMPLE_COUNT: u32 = 32u; - @compute @workgroup_size(1024, 1, 1) fn sample_radiance(@builtin(workgroup_id) workgroup_id: vec3, @builtin(global_invocation_id) active_cell_id: vec3) { if active_cell_id.x < world_cache_active_cells_count { @@ -37,7 +37,8 @@ fn sample_radiance(@builtin(workgroup_id) workgroup_id: vec3, @builtin(glob let ray_hit = trace_ray(geometry_data.world_position, ray_direction, RAY_T_MIN, RAY_T_MAX, RAY_FLAG_NONE); if ray_hit.kind != RAY_QUERY_INTERSECTION_NONE { let ray_hit = resolve_ray_hit_full(ray_hit); - new_radiance += ray_hit.material.base_color * query_world_cache(ray_hit.world_position, ray_hit.geometric_world_normal, view.world_position, &rng); +let cell_life = atomicLoad(&world_cache_life[cell_index]); + new_radiance += ray_hit.material.base_color * query_world_cache(ray_hit.world_position, ray_hit.geometric_world_normal, view.world_position, cell_life, &rng); } #endif @@ -68,8 +69,8 @@ fn sample_random_light_ris(world_position: vec3, world_normal: vec3, w var selected_sample_radiance = vec3(0.0); var selected_sample_target_function = 0.0; var selected_sample_world_position = vec4(0.0); - let mis_weight = 1.0 / f32(DIRECT_LIGHT_SAMPLE_COUNT); - for (var i = 0u; i < DIRECT_LIGHT_SAMPLE_COUNT; i++) { + let mis_weight = 1.0 / f32(WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT); + for (var i = 0u; i < WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT; i++) { let tile_sample = light_tile_start + rand_range_u(1024u, rng); let resolved_light_sample = unpack_resolved_light_sample(light_tile_resolved_samples[tile_sample], view.exposure); let light_contribution = calculate_resolved_light_contribution(resolved_light_sample, world_position, world_normal); From f45532cd0923ff7e3aebda5a1ad30c2adef95a93 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:29:25 -0500 Subject: [PATCH 08/11] Misc --- .../src/realtime/world_cache_query.wgsl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_solari/src/realtime/world_cache_query.wgsl b/crates/bevy_solari/src/realtime/world_cache_query.wgsl index b86cc4702303f..eedfa0bf3f291 100644 --- a/crates/bevy_solari/src/realtime/world_cache_query.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_query.wgsl @@ -58,21 +58,21 @@ fn query_world_cache(world_position: vec3, world_normal: vec3, view_po for (var i = 0u; i < WORLD_CACHE_MAX_SEARCH_STEPS; i++) { let existing_checksum = atomicCompareExchangeWeak(&world_cache_checksums[key], WORLD_CACHE_EMPTY_CELL, checksum).old_value; - if existing_checksum == checksum { - // Cache entry already exists - get radiance and reset cell lifetime + + // Cell already exists or is empty - reset lifetime + if existing_checksum == checksum || existing_checksum == WORLD_CACHE_EMPTY_CELL { #ifndef WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME atomicStore(&world_cache_life[key], cell_lifetime); -#elseif +#else atomicMax(&world_cache_life[key], cell_lifetime); #endif + } + + if existing_checksum == checksum { + // Cache entry already exists - get radiance return world_cache_radiance[key].rgb; } else if existing_checksum == WORLD_CACHE_EMPTY_CELL { - // Cell is empty - reset cell lifetime so that it starts getting updated next frame -#ifndef WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME - atomicStore(&world_cache_life[key], cell_lifetime); -#elseif - atomicMax(&world_cache_life[key], cell_lifetime); -#endif + // Cell is empty - initialize it world_cache_geometry_data[key].world_position = jittered_position; world_cache_geometry_data[key].world_normal = world_normal; return vec3(0.0); From 918ae8929ab2abfa277ffa3f9e547ae585ddf99a Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:05:54 -0500 Subject: [PATCH 09/11] Fixx off-by-1 bug --- crates/bevy_solari/src/realtime/world_cache_compact.wgsl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_solari/src/realtime/world_cache_compact.wgsl b/crates/bevy_solari/src/realtime/world_cache_compact.wgsl index 71585223a5e44..eae5cd5ff8ed4 100644 --- a/crates/bevy_solari/src/realtime/world_cache_compact.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_compact.wgsl @@ -59,12 +59,14 @@ fn compact_world_cache_write_active_cells( @builtin(local_invocation_index) thread_index: u32, ) { let compacted_index = world_cache_a[cell_id.x] + world_cache_b[workgroup_id.x]; - if world_cache_life[cell_id.x] != 0u { + let cell_active = world_cache_life[cell_id.x] != 0u; + + if cell_active { world_cache_active_cell_indices[compacted_index] = cell_id.x; } if thread_index == 1023u && workgroup_id.x == 1023u { - world_cache_active_cells_count = compacted_index + 1u; // TODO: This is 1 even when there are zero active entries in the cache + world_cache_active_cells_count = compacted_index + u32(cell_active); world_cache_active_cells_dispatch = vec3((world_cache_active_cells_count + 1023u) / 1024u, 1u, 1u); } } From 98b4f9be8bc44e3bdacdd19d0e23f93103948202 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:55:47 -0500 Subject: [PATCH 10/11] Fix rebase --- .../bevy_solari/src/realtime/world_cache_compact.wgsl | 2 +- crates/bevy_solari/src/realtime/world_cache_query.wgsl | 10 ++++++---- .../bevy_solari/src/realtime/world_cache_update.wgsl | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/bevy_solari/src/realtime/world_cache_compact.wgsl b/crates/bevy_solari/src/realtime/world_cache_compact.wgsl index eae5cd5ff8ed4..2bd22b86f5a15 100644 --- a/crates/bevy_solari/src/realtime/world_cache_compact.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_compact.wgsl @@ -67,6 +67,6 @@ fn compact_world_cache_write_active_cells( if thread_index == 1023u && workgroup_id.x == 1023u { world_cache_active_cells_count = compacted_index + u32(cell_active); - world_cache_active_cells_dispatch = vec3((world_cache_active_cells_count + 1023u) / 1024u, 1u, 1u); + world_cache_active_cells_dispatch = vec3((world_cache_active_cells_count + 63u) / 64u, 1u, 1u); } } diff --git a/crates/bevy_solari/src/realtime/world_cache_query.wgsl b/crates/bevy_solari/src/realtime/world_cache_query.wgsl index 76b7bfcbe5277..69a331bacc491 100644 --- a/crates/bevy_solari/src/realtime/world_cache_query.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_query.wgsl @@ -5,19 +5,21 @@ /// How responsive the world cache is to changes in lighting (higher is less responsive, lower is more responsive) const WORLD_CACHE_MAX_TEMPORAL_SAMPLES: f32 = 10.0; +/// How many direct light samples each cell takes when updating each frame +const WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT: u32 = 32u; +/// Maximum amount of distance to trace GI rays between two cache cells +const WORLD_CACHE_MAX_GI_RAY_DISTANCE: f32 = 4.0; + /// Maximum amount of frames a cell can live for without being queried const WORLD_CACHE_CELL_LIFETIME: u32 = 30u; /// Maximum amount of attempts to find a cache entry after a hash collision const WORLD_CACHE_MAX_SEARCH_STEPS: u32 = 3u; -/// The size of a cache cell at the lowest LOD in meters +/// Size of a cache cell at the lowest LOD in meters const WORLD_CACHE_POSITION_BASE_CELL_SIZE: f32 = 0.25; /// How fast the world cache transitions between LODs as a function of distance to the camera const WORLD_CACHE_POSITION_LOD_SCALE: f32 = 8.0; -/// How many direct light samples each cell takes when updating each frame -const WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT: u32 = 32u; - /// Marker value for an empty cell const WORLD_CACHE_EMPTY_CELL: u32 = 0u; diff --git a/crates/bevy_solari/src/realtime/world_cache_update.wgsl b/crates/bevy_solari/src/realtime/world_cache_update.wgsl index 5074808ae297d..5f3badb389c29 100644 --- a/crates/bevy_solari/src/realtime/world_cache_update.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_update.wgsl @@ -6,11 +6,12 @@ #import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, RAY_T_MIN} #import bevy_solari::world_cache::{ WORLD_CACHE_MAX_TEMPORAL_SAMPLES, -WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT, + WORLD_CACHE_DIRECT_LIGHT_SAMPLE_COUNT, + WORLD_CACHE_MAX_GI_RAY_DISTANCE, query_world_cache, world_cache_active_cells_count, world_cache_active_cell_indices, -world_cache_life, + world_cache_life, world_cache_geometry_data, world_cache_radiance, world_cache_active_cells_new_radiance, @@ -34,7 +35,7 @@ fn sample_radiance(@builtin(workgroup_id) workgroup_id: vec3, @builtin(glob #ifndef NO_MULTIBOUNCE let ray_direction = sample_cosine_hemisphere(geometry_data.world_normal, &rng); - let ray_hit = trace_ray(geometry_data.world_position, ray_direction, RAY_T_MIN, MAX_GI_RAY_DISTANCE, RAY_FLAG_NONE); + let ray_hit = trace_ray(geometry_data.world_position, ray_direction, RAY_T_MIN, WORLD_CACHE_MAX_GI_RAY_DISTANCE, RAY_FLAG_NONE); if ray_hit.kind != RAY_QUERY_INTERSECTION_NONE { let ray_hit = resolve_ray_hit_full(ray_hit); let cell_life = atomicLoad(&world_cache_life[cell_index]); From 0a96636747acfec1f982273377e8196dc88f4e22 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:57:02 -0500 Subject: [PATCH 11/11] Fmt --- crates/bevy_solari/src/realtime/world_cache_update.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_solari/src/realtime/world_cache_update.wgsl b/crates/bevy_solari/src/realtime/world_cache_update.wgsl index 5f3badb389c29..f80094e0dffc6 100644 --- a/crates/bevy_solari/src/realtime/world_cache_update.wgsl +++ b/crates/bevy_solari/src/realtime/world_cache_update.wgsl @@ -38,7 +38,7 @@ fn sample_radiance(@builtin(workgroup_id) workgroup_id: vec3, @builtin(glob let ray_hit = trace_ray(geometry_data.world_position, ray_direction, RAY_T_MIN, WORLD_CACHE_MAX_GI_RAY_DISTANCE, RAY_FLAG_NONE); if ray_hit.kind != RAY_QUERY_INTERSECTION_NONE { let ray_hit = resolve_ray_hit_full(ray_hit); -let cell_life = atomicLoad(&world_cache_life[cell_index]); + let cell_life = atomicLoad(&world_cache_life[cell_index]); new_radiance += ray_hit.material.base_color * query_world_cache(ray_hit.world_position, ray_hit.geometric_world_normal, view.world_position, cell_life, &rng); } #endif