diff --git a/crates/bevy_solari/src/realtime/specular_gi.wgsl b/crates/bevy_solari/src/realtime/specular_gi.wgsl index 176be1c619258..58b400076cdef 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_random_light, random_emissive_light_pdf, sample_ggx_vndf, ggx_vndf_pdf, power_heuristic} #import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, ResolvedRayHitFull, RAY_T_MIN, RAY_T_MAX} -#import bevy_solari::world_cache::{query_world_cache, WORLD_CACHE_CELL_LIFETIME} +#import bevy_solari::world_cache::{query_world_cache, get_cell_size, WORLD_CACHE_CELL_LIFETIME} @group(1) @binding(0) var view_output: texture_storage_2d; @group(1) @binding(5) var gi_reservoirs_a: array; @@ -16,7 +16,7 @@ struct PushConstants { frame_index: u32, reset: u32 } var constants: PushConstants; const DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD: f32 = 0.4; -const WORLD_CACHE_TERMINATION_ROUGHNESS_THRESHOLD: f32 = 0.4; +const TERMINATE_IN_WORLD_CACHE_THRESHOLD: f32 = 0.03; @compute @workgroup_size(8, 8, 1) fn specular_gi(@builtin(global_invocation_id) global_id: vec3) { @@ -31,7 +31,8 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3) { } let surface = gpixel_resolve(textureLoad(gbuffer, global_id.xy, 0), depth, global_id.xy, view.main_pass_viewport.zw, view.world_from_clip); - let wo = normalize(view.world_position - surface.world_position); + let wo_unnormalized = view.world_position - surface.world_position; + let wo = normalize(wo_unnormalized); var radiance: vec3; var wi: vec3; @@ -51,7 +52,12 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3) { wi = wi_tangent.x * T + wi_tangent.y * B + wi_tangent.z * N; let pdf = ggx_vndf_pdf(wo_tangent, wi_tangent, surface.material.roughness); - radiance = trace_glossy_path(surface.world_position, wi, &rng) / pdf; + // https://d1qx31qr3h6wln.cloudfront.net/publications/mueller21realtime.pdf#subsection.3.4, equation (4) + let cos_theta = saturate(dot(wo, surface.world_normal)); + var a0 = dot(wo_unnormalized, wo_unnormalized) / (4.0 * PI * cos_theta); + a0 *= TERMINATE_IN_WORLD_CACHE_THRESHOLD; + + radiance = trace_glossy_path(surface.world_position, wi, pdf, a0, &rng) / pdf; } let brdf = evaluate_specular_brdf(surface.world_normal, wo, wi, surface.material.base_color, surface.material.metallic, @@ -68,11 +74,12 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3) { #endif } -fn trace_glossy_path(initial_ray_origin: vec3, initial_wi: vec3, rng: ptr) -> vec3 { +fn trace_glossy_path(initial_ray_origin: vec3, initial_wi: vec3, initial_p_bounce: f32, a0: f32, rng: ptr) -> vec3 { var ray_origin = initial_ray_origin; var wi = initial_wi; + var p_bounce = initial_p_bounce; var surface_perfectly_specular = false; - var p_bounce = 0.0; + var path_spread = 0.0; // Trace up to three bounces, getting the net throughput from them var radiance = vec3(0.0); @@ -99,8 +106,11 @@ fn trace_glossy_path(initial_ray_origin: vec3, initial_wi: vec3, rng: // Should not perform NEE for mirror-like surfaces surface_perfectly_specular = ray_hit.material.roughness <= 0.001 && ray_hit.material.metallic > 0.9999; - if ray_hit.material.roughness > WORLD_CACHE_TERMINATION_ROUGHNESS_THRESHOLD && i != 0u { - // Surface is very rough, terminate path in the world cache + // https://d1qx31qr3h6wln.cloudfront.net/publications/mueller21realtime.pdf#subsection.3.4, equation (3) + path_spread += sqrt((ray.t * ray.t) / (p_bounce * wo_tangent.z)); + + if path_spread * path_spread > a0 * get_cell_size(ray_hit.world_position, view.world_position) { + // Path spread is wide enough, terminate path in the world cache 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, WORLD_CACHE_CELL_LIFETIME, rng); break;