Skip to content

Commit e6b7a12

Browse files
authored
Solari: Specular path spread heuristic (bevyengine#22311)
Currently solari's specular GI conditionally terminates in the world cache based on the roughness of a surface. This PR changes that. Now, solari tracks the spread of the full path to the camera, and terminates in the world cache once the spread is wide enough. On the plus side this gives * A good bit better performance * More stable / higher quality lighting sometimes On the downside: * Transitions between world cache LODs tend to show up in rough reflections. Unlike ReSTIR GI, there's currently no temporal reservoirs to hide it. * Slightly more light leaks / less accuracy sometimes Overall I think it's a worthwhile change.
1 parent edde626 commit e6b7a12

File tree

1 file changed

+18
-8
lines changed

1 file changed

+18
-8
lines changed

crates/bevy_solari/src/realtime/specular_gi.wgsl

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#import bevy_solari::gbuffer_utils::gpixel_resolve
66
#import bevy_solari::sampling::{sample_random_light, random_emissive_light_pdf, sample_ggx_vndf, ggx_vndf_pdf, power_heuristic}
77
#import bevy_solari::scene_bindings::{trace_ray, resolve_ray_hit_full, ResolvedRayHitFull, RAY_T_MIN, RAY_T_MAX}
8-
#import bevy_solari::world_cache::{query_world_cache, WORLD_CACHE_CELL_LIFETIME}
8+
#import bevy_solari::world_cache::{query_world_cache, get_cell_size, WORLD_CACHE_CELL_LIFETIME}
99

1010
@group(1) @binding(0) var view_output: texture_storage_2d<rgba16float, read_write>;
1111
@group(1) @binding(5) var<storage, read_write> gi_reservoirs_a: array<Reservoir>;
@@ -16,7 +16,7 @@ struct PushConstants { frame_index: u32, reset: u32 }
1616
var<push_constant> constants: PushConstants;
1717

1818
const DIFFUSE_GI_REUSE_ROUGHNESS_THRESHOLD: f32 = 0.4;
19-
const WORLD_CACHE_TERMINATION_ROUGHNESS_THRESHOLD: f32 = 0.4;
19+
const TERMINATE_IN_WORLD_CACHE_THRESHOLD: f32 = 0.03;
2020

2121
@compute @workgroup_size(8, 8, 1)
2222
fn specular_gi(@builtin(global_invocation_id) global_id: vec3<u32>) {
@@ -31,7 +31,8 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3<u32>) {
3131
}
3232
let surface = gpixel_resolve(textureLoad(gbuffer, global_id.xy, 0), depth, global_id.xy, view.main_pass_viewport.zw, view.world_from_clip);
3333

34-
let wo = normalize(view.world_position - surface.world_position);
34+
let wo_unnormalized = view.world_position - surface.world_position;
35+
let wo = normalize(wo_unnormalized);
3536

3637
var radiance: vec3<f32>;
3738
var wi: vec3<f32>;
@@ -51,7 +52,12 @@ fn specular_gi(@builtin(global_invocation_id) global_id: vec3<u32>) {
5152
wi = wi_tangent.x * T + wi_tangent.y * B + wi_tangent.z * N;
5253
let pdf = ggx_vndf_pdf(wo_tangent, wi_tangent, surface.material.roughness);
5354

54-
radiance = trace_glossy_path(surface.world_position, wi, &rng) / pdf;
55+
// https://d1qx31qr3h6wln.cloudfront.net/publications/mueller21realtime.pdf#subsection.3.4, equation (4)
56+
let cos_theta = saturate(dot(wo, surface.world_normal));
57+
var a0 = dot(wo_unnormalized, wo_unnormalized) / (4.0 * PI * cos_theta);
58+
a0 *= TERMINATE_IN_WORLD_CACHE_THRESHOLD;
59+
60+
radiance = trace_glossy_path(surface.world_position, wi, pdf, a0, &rng) / pdf;
5561
}
5662

5763
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<u32>) {
6874
#endif
6975
}
7076

71-
fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, rng: ptr<function, u32>) -> vec3<f32> {
77+
fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, initial_p_bounce: f32, a0: f32, rng: ptr<function, u32>) -> vec3<f32> {
7278
var ray_origin = initial_ray_origin;
7379
var wi = initial_wi;
80+
var p_bounce = initial_p_bounce;
7481
var surface_perfectly_specular = false;
75-
var p_bounce = 0.0;
82+
var path_spread = 0.0;
7683

7784
// Trace up to three bounces, getting the net throughput from them
7885
var radiance = vec3(0.0);
@@ -99,8 +106,11 @@ fn trace_glossy_path(initial_ray_origin: vec3<f32>, initial_wi: vec3<f32>, rng:
99106
// Should not perform NEE for mirror-like surfaces
100107
surface_perfectly_specular = ray_hit.material.roughness <= 0.001 && ray_hit.material.metallic > 0.9999;
101108

102-
if ray_hit.material.roughness > WORLD_CACHE_TERMINATION_ROUGHNESS_THRESHOLD && i != 0u {
103-
// Surface is very rough, terminate path in the world cache
109+
// https://d1qx31qr3h6wln.cloudfront.net/publications/mueller21realtime.pdf#subsection.3.4, equation (3)
110+
path_spread += sqrt((ray.t * ray.t) / (p_bounce * wo_tangent.z));
111+
112+
if path_spread * path_spread > a0 * get_cell_size(ray_hit.world_position, view.world_position) {
113+
// Path spread is wide enough, terminate path in the world cache
104114
let diffuse_brdf = ray_hit.material.base_color / PI;
105115
radiance += throughput * diffuse_brdf * query_world_cache(ray_hit.world_position, ray_hit.geometric_world_normal, view.world_position, WORLD_CACHE_CELL_LIFETIME, rng);
106116
break;

0 commit comments

Comments
 (0)