diff --git a/blade-render/code/ray-trace.wgsl b/blade-render/code/ray-trace.wgsl index 8b30b7b2..0d244adb 100644 --- a/blade-render/code/ray-trace.wgsl +++ b/blade-render/code/ray-trace.wgsl @@ -15,6 +15,9 @@ const PAIRWISE_MIS: bool = true; // 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, @@ -54,6 +57,7 @@ struct LiveReservoir { selected_uv: vec2, selected_light_index: u32, selected_target_score: f32, + radiance: vec3, weight_sum: f32, history: f32, } @@ -81,9 +85,10 @@ fn bump_reservoir(r: ptr, history: f32) { } fn make_reservoir(ls: LightSample, light_index: u32, brdf: vec3) -> LiveReservoir { var r: LiveReservoir; + r.radiance = ls.radiance * brdf; r.selected_uv = ls.uv; r.selected_light_index = light_index; - r.selected_target_score = compute_target_score(ls.radiance * brdf); + r.selected_target_score = compute_target_score(r.radiance); r.weight_sum = r.selected_target_score / ls.pdf; r.history = 1.0; return r; @@ -95,6 +100,7 @@ fn merge_reservoir(r: ptr, other: LiveReservoir, random (*r).selected_light_index = other.selected_light_index; (*r).selected_uv = other.selected_uv; (*r).selected_target_score = other.selected_target_score; + (*r).radiance = other.radiance; return true; } else { return false; @@ -105,6 +111,7 @@ fn unpack_reservoir(f: StoredReservoir, max_history: u32) -> LiveReservoir { r.selected_light_index = f.light_index; r.selected_uv = f.light_uv; r.selected_target_score = f.target_score; + r.radiance = vec3(0.0); // to be continued... let history = min(f.confidence, f32(max_history)); r.weight_sum = f.contribution_weight * f.target_score * history; r.history = history; @@ -238,34 +245,36 @@ fn evaluate_reflected_light(surface: Surface, light_index: u32, light_uv: vec2, score: f32, } -fn make_target_pdf(color: vec3) -> TargetPdf { - return TargetPdf(color, compute_target_score(color)); +fn make_target_score(color: vec3) -> TargetScore { + return TargetScore(color, compute_target_score(color)); } -fn estimate_target_pdf_with_occlusion(surface: Surface, position: vec3, light_index: u32, light_uv: vec2, debug_len: f32) -> TargetPdf { +fn estimate_target_score_with_occlusion( + surface: Surface, position: vec3, light_index: u32, light_uv: vec2, debug_len: f32 +) -> TargetScore { if (light_index != 0u) { - return TargetPdf(); + return TargetScore(); } let direction = map_equirect_uv_to_dir(light_uv); if (dot(direction, surface.flat_normal) <= 0.0) { - return TargetPdf(); + return TargetScore(); } let brdf = evaluate_brdf(surface, direction); if (brdf <= 0.0) { - return TargetPdf(); + return TargetScore(); } if (check_ray_occluded(position, direction, debug_len)) { - return TargetPdf(); + return TargetScore(); } 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); + return make_target_score(brdf * radiance); } } @@ -331,7 +340,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) { @@ -343,9 +351,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_reservoir(&canonical, other, random_gen(rng)); } else { bump_reservoir(&canonical, 1.0); } @@ -396,13 +402,13 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(0.0); var mis_canonical = BASE_CANONICAL_MIS; + var color_and_weight = vec4(0.0); for (var rid = 0u; rid < accepted_count; rid += 1u) { let neighbor_index = accepted_reservoir_indices[rid]; let neighbor = prev_reservoirs[neighbor_index]; let max_history = select(parameters.spatial_tap_history, parameters.temporal_history, rid == temporal_index); var other: LiveReservoir; - var other_color: vec3; if (PAIRWISE_MIS) { let neighbor_pixel = get_pixel_from_reservoir_index(neighbor_index, prev_camera); let neighbor_history = min(neighbor.confidence, f32(max_history)); @@ -411,7 +417,7 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr, rng: ptr, rng: ptr(neighbor.contribution_weight * other.radiance, 1.0); + } if (other.weight_sum <= 0.0) { bump_reservoir(&reservoir, other.history); - } else if (merge_reservoir(&reservoir, other, random_gen(rng))) { - shaded_color = other_color; + } else { + merge_reservoir(&reservoir, other, random_gen(rng)); } } @@ -454,15 +463,22 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(cw * canonical.radiance, 1.0); } + merge_reservoir(&reservoir, canonical, random_gen(rng)); let effective_history = select(reservoir.history, BASE_CANONICAL_MIS + f32(accepted_count), PAIRWISE_MIS); let stored = pack_reservoir_detail(reservoir, effective_history); reservoirs[pixel_index] = stored; var ro = RestirOutput(); - ro.radiance = stored.contribution_weight * shaded_color; + if (DECOUPLED_SHADING) { + ro.radiance = color_and_weight.xyz / max(color_and_weight.w, 0.001); + } else { + ro.radiance = stored.contribution_weight * reservoir.radiance; + } return ro; } diff --git a/blade-render/src/render/mod.rs b/blade-render/src/render/mod.rs index 84a42e49..c50f411d 100644 --- a/blade-render/src/render/mod.rs +++ b/blade-render/src/render/mod.rs @@ -93,7 +93,8 @@ pub struct DenoiserConfig { } pub struct SelectionInfo { - pub std_deviation: Option>, + pub std_deviation: mint::Vector3, + pub std_deviation_history: u32, pub tex_coords: mint::Vector2, pub base_color_texture: Option>, pub normal_texture: Option>, @@ -101,7 +102,8 @@ pub struct SelectionInfo { impl Default for SelectionInfo { fn default() -> Self { Self { - std_deviation: None, + std_deviation: [0.0; 3].into(), + std_deviation_history: 0, tex_coords: [0.0; 2].into(), base_color_texture: None, normal_texture: None, @@ -1128,18 +1130,18 @@ impl Renderer { } else { transfer.fill_buffer(self.debug.buffer.at(20), 4, 0); } - if accumulate_variance { - // copy the previous frame variance - transfer.copy_buffer_to_buffer( + if reset_reservoirs || !accumulate_variance { + transfer.fill_buffer( self.debug.buffer.at(32), - self.debug.variance_buffer.into(), mem::size_of::() as u64, + 0, ); } else { - transfer.fill_buffer( + // copy the previous frame variance + transfer.copy_buffer_to_buffer( self.debug.buffer.at(32), + self.debug.variance_buffer.into(), mem::size_of::() as u64, - 0, ); } transfer.copy_buffer_to_buffer( @@ -1365,17 +1367,18 @@ impl Renderer { let db_e = unsafe { &*(self.debug.entry_buffer.data() as *const DebugEntry) }; SelectionInfo { std_deviation: if db_v.count == 0 { - None + [0.0; 3].into() } else { let sum_avg = glam::Vec3::from(db_v.color_sum) / (db_v.count as f32); let sum2_avg = glam::Vec3::from(db_v.color2_sum) / (db_v.count as f32); let variance = sum2_avg - sum_avg * sum_avg; - Some(mint::Vector3 { + mint::Vector3 { x: variance.x.sqrt(), y: variance.y.sqrt(), z: variance.z.sqrt(), - }) + } }, + std_deviation_history: db_v.count, tex_coords: db_e.tex_coords.into(), base_color_texture: self .texture_resource_lookup diff --git a/examples/scene/main.rs b/examples/scene/main.rs index 55856535..546d0206 100644 --- a/examples/scene/main.rs +++ b/examples/scene/main.rs @@ -275,7 +275,7 @@ impl Example { } } - self.renderer.hot_reload( + self.need_accumulation_reset |= self.renderer.hot_reload( &self.asset_hub, &self.context, self.prev_sync_point.as_ref().unwrap(), @@ -444,23 +444,39 @@ impl Example { ui.checkbox(&mut enabled, name); self.debug.texture_flags.set(bit, enabled); } - // reset accumulation - self.need_accumulation_reset |= ui.button("reset").clicked(); // selection info let mut selection = blade_render::SelectionInfo::default(); if let Some(screen_pos) = self.debug.mouse_pos { selection = self.renderer.read_debug_selection_info(); - let sd = selection.std_deviation.unwrap_or([0.0; 3].into()); let style = ui.style(); egui::Frame::group(style).show(ui, |ui| { - ui.label(format!("Pixel: {screen_pos:?}")); ui.horizontal(|ui| { + ui.label("Pixel:"); + ui.colored_label( + egui::Color32::WHITE, + format!("{}x{}", screen_pos[0], screen_pos[1]), + ); + if ui.button("Unselect").clicked() { + self.debug.mouse_pos = None; + } + }); + ui.horizontal(|ui| { + let sd = &selection.std_deviation; ui.label("Std Deviation:"); ui.colored_label( egui::Color32::WHITE, format!("{:.2} {:.2} {:.2}", sd.x, sd.y, sd.z), ); }); + ui.horizontal(|ui| { + ui.label("Samples:"); + let power = selection + .std_deviation_history + .next_power_of_two() + .trailing_zeros(); + ui.colored_label(egui::Color32::WHITE, format!("2^{}", power)); + self.need_accumulation_reset |= ui.button("Reset").clicked(); + }); ui.horizontal(|ui| { ui.label("Texture coords:"); ui.colored_label( @@ -495,9 +511,6 @@ impl Example { ui.colored_label(egui::Color32::WHITE, name); } }); - if ui.button("Unselect").clicked() { - self.debug.mouse_pos = None; - } }); } // blits