From e8cb677d5e2e99c8db0fdc9cc48b61c4a05d728b Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Tue, 17 Oct 2023 22:36:51 -0700 Subject: [PATCH] Support decoupled shading from reservoirs This is disabled by default because it clearly produces a lot of fireflies on the default scene. --- blade-render/code/ray-trace.wgsl | 89 ++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/blade-render/code/ray-trace.wgsl b/blade-render/code/ray-trace.wgsl index 253862fa..9c21bea1 100644 --- a/blade-render/code/ray-trace.wgsl +++ b/blade-render/code/ray-trace.wgsl @@ -8,13 +8,16 @@ const PI: f32 = 3.1415926; const MAX_RESERVOIRS: u32 = 2u; -// See "9.1 pairwise mis for robust reservoir reuse" +// See "9.1 pairwise mis for robust reservoir reuse" in // "Correlations and Reuse for Fast and Accurate Physically Based Light Transport" const PAIRWISE_MIS: bool = true; // Base MIS for canonical samples. The constant isolates a critical difference between // Bitterli's pseudocode (where it's 1) and NVidia's RTXDI implementation (where it's 0). // With Bitterli's 1 we have MIS not respecting the prior history enough. const BASE_CANONICAL_MIS: f32 = 0.05; +// See "DECOUPLING SHADING AND REUSE" in +// "Rearchitecting Spatiotemporal Resampling for Production" +const DECOUPLED_SHADING: bool = false; struct MainParams { frame_index: u32, @@ -56,6 +59,7 @@ struct LiveReservoir { selected_target_score: f32, weight_sum: f32, history: f32, + shaded_color: vec3 } fn compute_target_score(radiance: vec3) -> f32 { @@ -79,27 +83,67 @@ fn get_pixel_from_reservoir_index(index: i32, camera: CameraParams) -> vec2 fn bump_reservoir(r: ptr, history: f32) { (*r).history += history; } -fn make_reservoir(ls: LightSample, light_index: u32, brdf: vec3) -> LiveReservoir { + +fn make_reservoir_from_light_sample(ls: LightSample, light_index: u32, brdf: vec3) -> LiveReservoir { + let shaded = ls.radiance * brdf; + let target_score = compute_target_score(shaded); var r: LiveReservoir; r.selected_uv = ls.uv; r.selected_light_index = light_index; - r.selected_target_score = compute_target_score(ls.radiance * brdf); - r.weight_sum = r.selected_target_score / ls.pdf; + r.selected_target_score = target_score; + r.weight_sum = target_score / ls.pdf; r.history = 1.0; + r.shaded_color = shaded; return r; } + +fn merge_light_sample(r: ptr, ls: LightSample, light_index: u32, brdf: vec3, random: f32) -> bool { + let shaded = ls.radiance * brdf; + let target_score = compute_target_score(shaded); + if (target_score <= 0.0) { + return false; + } + + let weight = target_score / ls.pdf; + (*r).weight_sum += weight; + (*r).history += 1.0; + + if ((*r).weight_sum * random < weight) { + (*r).selected_light_index = light_index; + (*r).selected_uv = ls.uv; + (*r).selected_target_score = target_score; + (*r).shaded_color = shaded; + return true; + } else { + return false; + } +} + fn merge_reservoir(r: ptr, other: LiveReservoir, random: f32) -> bool { (*r).weight_sum += other.weight_sum; (*r).history += other.history; - if ((*r).weight_sum * random < other.weight_sum) { + if (other.selected_target_score <= 0.0) { + return false; + } + + let ratio = other.weight_sum / (*r).weight_sum; + if (DECOUPLED_SHADING) { + (*r).shaded_color = mix((*r).shaded_color, other.shaded_color, ratio); + } + + if (random < ratio) { (*r).selected_light_index = other.selected_light_index; (*r).selected_uv = other.selected_uv; (*r).selected_target_score = other.selected_target_score; + if (!DECOUPLED_SHADING) { + (*r).shaded_color = other.shaded_color; + } return true; } else { return false; } } + fn unpack_reservoir(f: StoredReservoir, max_history: u32) -> LiveReservoir { var r: LiveReservoir; r.selected_light_index = f.light_index; @@ -108,6 +152,7 @@ fn unpack_reservoir(f: StoredReservoir, max_history: u32) -> LiveReservoir { let history = min(f.confidence, f32(max_history)); r.weight_sum = f.contribution_weight * f.target_score * history; r.history = history; + r.shaded_color = vec3(0.0); // needs to be updated return r; } fn pack_reservoir_detail(r: LiveReservoir, denom_factor: f32) -> StoredReservoir { @@ -261,11 +306,10 @@ fn estimate_target_pdf_with_occlusion(surface: Surface, position: vec3, lig if (check_ray_occluded(position, direction, debug_len)) { return TargetPdf(); - } else { - //Note: same as `evaluate_reflected_light` - let radiance = textureSampleLevel(env_map, sampler_nearest, light_uv, 0.0).xyz; - return make_target_pdf(brdf * radiance); } + //Note: same as `evaluate_reflected_light` + let radiance = textureSampleLevel(env_map, sampler_nearest, light_uv, 0.0).xyz; + return make_target_pdf(brdf * radiance); } fn evaluate_sample(ls: LightSample, surface: Surface, start_pos: vec3, debug_len: f32) -> f32 { @@ -331,7 +375,6 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(0.0); for (var i = 0u; i < parameters.num_environment_samples; i += 1u) { var ls: LightSample; if (parameters.environment_importance_sampling != 0u) { @@ -342,10 +385,7 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr 0.0) { - let other = make_reservoir(ls, 0u, vec3(brdf)); - if (merge_reservoir(&canonical, other, random_gen(rng))) { - canonical_radiance = ls.radiance * brdf; - } + merge_light_sample(&canonical, ls, 0u, vec3(brdf), random_gen(rng)); } else { bump_reservoir(&canonical, 1.0); } @@ -394,7 +434,6 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(0.0); var mis_canonical = BASE_CANONICAL_MIS; for (var rid = 0u; rid < accepted_count; rid += 1u) { let neighbor_index = accepted_reservoir_indices[rid]; @@ -402,7 +441,7 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr; + var other_history = other.history; //TODO if (PAIRWISE_MIS) { let neighbor_pixel = get_pixel_from_reservoir_index(neighbor_index, prev_camera); let neighbor_history = min(neighbor.confidence, f32(max_history)); @@ -435,18 +474,18 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr, rng: ptr