From af8884d2a7b797fd10025de2665d275b44438b00 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Mon, 14 Oct 2024 23:46:04 -0700 Subject: [PATCH] Unify temporal and spatial taps --- blade-helpers/src/hud.rs | 14 ++--- blade-helpers/src/lib.rs | 9 ++-- blade-render/code/ray-trace.wgsl | 89 ++++++++++++-------------------- blade-render/src/render/mod.rs | 27 +++++----- 4 files changed, 54 insertions(+), 85 deletions(-) diff --git a/blade-helpers/src/hud.rs b/blade-helpers/src/hud.rs index 880b745f..207df770 100644 --- a/blade-helpers/src/hud.rs +++ b/blade-helpers/src/hud.rs @@ -13,18 +13,14 @@ impl ExposeHud for blade_render::RayConfig { &mut self.environment_importance_sampling, "Env importance sampling", ); - ui.checkbox(&mut self.temporal_tap, "Temporal tap"); + ui.add(egui::widgets::Slider::new(&mut self.tap_count, 0..=10).text("Tap count")); + ui.add(egui::widgets::Slider::new(&mut self.tap_radius, 1..=50).text("Tap radius (px)")); ui.add( - egui::widgets::Slider::new(&mut self.temporal_history, 0..=50).text("Temporal history"), + egui::widgets::Slider::new(&mut self.tap_confidence_near, 1..=50) + .text("Max confidence"), ); - ui.add(egui::widgets::Slider::new(&mut self.spatial_taps, 0..=10).text("Spatial taps")); ui.add( - egui::widgets::Slider::new(&mut self.spatial_tap_history, 0..=50) - .text("Spatial tap history"), - ); - ui.add( - egui::widgets::Slider::new(&mut self.spatial_radius, 1..=50) - .text("Spatial radius (px)"), + egui::widgets::Slider::new(&mut self.tap_confidence_far, 1..=50).text("Min confidence"), ); ui.add( egui::widgets::Slider::new(&mut self.t_start, 0.001..=0.5) diff --git a/blade-helpers/src/lib.rs b/blade-helpers/src/lib.rs index db7f2ec8..e8872ec2 100644 --- a/blade-helpers/src/lib.rs +++ b/blade-helpers/src/lib.rs @@ -11,11 +11,10 @@ pub fn default_ray_config() -> blade_render::RayConfig { blade_render::RayConfig { num_environment_samples: 1, environment_importance_sampling: false, - temporal_tap: true, - temporal_history: 10, - spatial_taps: 1, - spatial_tap_history: 10, - spatial_radius: 20, + tap_count: 2, + tap_radius: 20, + tap_confidence_near: 15, + tap_confidence_far: 10, t_start: 0.01, pairwise_mis: true, defensive_mis: 0.1, diff --git a/blade-render/code/ray-trace.wgsl b/blade-render/code/ray-trace.wgsl index 168b93cd..24132ea4 100644 --- a/blade-render/code/ray-trace.wgsl +++ b/blade-render/code/ray-trace.wgsl @@ -11,27 +11,22 @@ const RAY_FLAG_CULL_NO_OPAQUE: u32 = 0x80u; const PI: f32 = 3.1415926; -const MAX_RESERVOIRS: u32 = 2u; +const MAX_RESERVOIRS: u32 = 4u; // See "DECOUPLING SHADING AND REUSE" in // "Rearchitecting Spatiotemporal Resampling for Production" const DECOUPLED_SHADING: bool = false; -// We are considering 2x2 grid, so must be <= 4 -const FACTOR_TEMPORAL_CANDIDATES: u32 = 1u; // How many more candidates to consder than the taps we need -const FACTOR_SPATIAL_CANDIDATES: u32 = 3u; -// Has to be at least discarding the 2x2 block -const MIN_SPATIAL_REUSE_DISTANCE: i32 = 7; +const FACTOR_CANDIDATES: u32 = 3u; struct MainParams { frame_index: u32, num_environment_samples: u32, environment_importance_sampling: u32, - temporal_tap: u32, - temporal_history: u32, - spatial_taps: u32, - spatial_tap_history: u32, - spatial_radius: i32, + tap_count: u32, + tap_radius: f32, + tap_confidence_near: f32, + tap_confidence_far: f32, t_start: f32, use_pairwise_mis: u32, defensive_mis: f32, @@ -124,13 +119,13 @@ fn normalize_reservoir(r: ptr, history: f32) { (*r).history = history; } } -fn unpack_reservoir(f: StoredReservoir, max_history: u32, radiance: vec3) -> LiveReservoir { +fn unpack_reservoir(f: StoredReservoir, max_confidence: f32, radiance: vec3) -> LiveReservoir { var r: LiveReservoir; r.selected_light_index = f.light_index; r.selected_uv = f.light_uv; r.selected_target_score = f.target_score; r.selected_radiance = radiance; - let history = min(f.confidence, f32(max_history)); + let history = min(f.confidence, max_confidence); r.weight_sum = f.contribution_weight * f.target_score * history; r.history = history; return r; @@ -234,7 +229,9 @@ fn evaluate_brdf(surface: Surface, dir: vec3) -> f32 { return lambert_brdf * max(0.0, lambert_term); } -fn check_ray_occluded(acs: acceleration_structure, position: vec3, direction: vec3, debug_len: f32) -> bool { +var debug_len: f32; + +fn check_ray_occluded(acs: acceleration_structure, position: vec3, direction: vec3, debug_len: f32, debug_color: u32) -> bool { var rq: ray_query; let flags = RAY_FLAG_TERMINATE_ON_FIRST_HIT | RAY_FLAG_CULL_NO_OPAQUE; rayQueryInitialize(&rq, acs, @@ -244,8 +241,8 @@ fn check_ray_occluded(acs: acceleration_structure, position: vec3, directio let intersection = rayQueryGetCommittedIntersection(&rq); let occluded = intersection.kind != RAY_QUERY_INTERSECTION_NONE; - if (debug_len != 0.0) { - let color = select(0xFFFFFFu, 0x0000FFu, occluded); + if (DEBUG_MODE && debug_len > 0.0) { + let color = select(0xFFFFFFu, 0x808080u, occluded) & debug_color; debug_line(position, position + debug_len * direction, color); } return occluded; @@ -284,7 +281,8 @@ fn make_target_score(color: vec3) -> TargetScore { } fn estimate_target_score_with_occlusion( - surface: Surface, position: vec3, light_index: u32, light_uv: vec2, acs: acceleration_structure, debug_len: f32 + surface: Surface, position: vec3, light_index: u32, light_uv: vec2, acs: acceleration_structure, + debug_len: f32, debug_color: u32, ) -> TargetScore { if (light_index != 0u) { return TargetScore(); @@ -298,7 +296,7 @@ fn estimate_target_score_with_occlusion( return TargetScore(); } - if (check_ray_occluded(acs, position, direction, debug_len)) { + if (check_ray_occluded(acs, position, direction, debug_len, debug_color)) { return TargetScore(); } else { //Note: same as `evaluate_reflected_light` @@ -307,7 +305,7 @@ fn estimate_target_score_with_occlusion( } } -fn evaluate_sample(ls: LightSample, surface: Surface, start_pos: vec3, debug_len: f32) -> f32 { +fn evaluate_sample(ls: LightSample, surface: Surface, start_pos: vec3, debug_len: f32, debug_color: u32) -> f32 { let dir = map_equirect_uv_to_dir(ls.uv); if (dot(dir, surface.flat_normal) <= 0.0) { return 0.0; @@ -323,7 +321,7 @@ fn evaluate_sample(ls: LightSample, surface: Surface, start_pos: vec3, debu return 0.0; } - if (check_ray_occluded(acc_struct, start_pos, dir, debug_len)) { + if (check_ray_occluded(acc_struct, start_pos, dir, debug_len, debug_color)) { return 0.0; } @@ -350,9 +348,9 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(1.0 / surface.depth)); } - let debug_len = select(0.0, surface.depth * 0.2, enable_debug); let position = camera.position + surface.depth * ray_dir; let normal = qrot(surface.basis, vec3(0.0, 0.0, 1.0)); + let debug_len = select(0.0, surface.depth * 0.2, enable_debug); var canonical = LiveReservoir(); for (var i = 0u; i < parameters.num_environment_samples; i += 1u) { @@ -363,7 +361,7 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr 0.0) { let other = make_reservoir(ls, 0u, vec3(brdf)); merge_reservoir(&canonical, other, random_gen(rng)); @@ -373,36 +371,17 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(center_coord); - // Trick to start with closer pixels: we derive the "further" - // pixel in 2x2 grid by considering the sum. - let further_pixel = vec2(center_coord - 0.5) + vec2(center_coord + 0.5) - center_pixel; // First, gather the list of reservoirs to merge with var accepted_reservoir_indices = array(); var accepted_count = 0u; - var temporal_index = ~0u; - let num_temporal_candidates = parameters.temporal_tap * FACTOR_TEMPORAL_CANDIDATES; - let num_candidates = num_temporal_candidates + parameters.spatial_taps * FACTOR_SPATIAL_CANDIDATES; - let max_samples = min(MAX_RESERVOIRS, 1u + parameters.spatial_taps); + let max_samples = min(MAX_RESERVOIRS, parameters.tap_count); + let num_candidates = max_samples * FACTOR_CANDIDATES; for (var tap = 0u; tap < num_candidates && accepted_count < max_samples; tap += 1u) { - var other_pixel = center_pixel; - if (tap < num_temporal_candidates) { - if (temporal_index < tap) { - continue; - } - let mask = vec2(tap) & vec2(1u, 2u); - other_pixel = select(center_pixel, further_pixel, mask != vec2(0u)); - } else { - let r0 = max(center_pixel - vec2(parameters.spatial_radius), vec2(0)); - let r1 = min(center_pixel + vec2(parameters.spatial_radius + 1), vec2(prev_camera.target_size)); - other_pixel = vec2(mix(vec2(r0), vec2(r1), vec2(random_gen(rng), random_gen(rng)))); - let diff = other_pixel - center_pixel; - if (dot(diff, diff) < MIN_SPATIAL_REUSE_DISTANCE) { - continue; - } - } + let radius = parameters.tap_radius * random_gen(rng); + let offset = radius * sample_circle(random_gen(rng)); + let other_pixel = vec2(center_coord + offset); let other_index = get_reservoir_index(other_pixel, prev_camera); if (other_index < 0) { @@ -419,9 +398,6 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr, rng: ptr(neighbor_pixel) - center_coord; + let max_confidence = mix(parameters.tap_confidence_near, parameters.tap_confidence_far, length(offset) / parameters.tap_radius); var other: LiveReservoir; if (parameters.use_pairwise_mis != 0u) { - let neighbor_pixel = get_pixel_from_reservoir_index(neighbor_index, prev_camera); - let neighbor_history = min(neighbor.confidence, f32(max_history)); + let neighbor_history = min(neighbor.confidence, max_confidence); { // scoping this to hint the register allocation let neighbor_surface = read_prev_surface(neighbor_pixel); let neighbor_dir = get_ray_direction(prev_camera, neighbor_pixel); let neighbor_position = prev_camera.position + neighbor_surface.depth * neighbor_dir; let t_canonical_at_neighbor = estimate_target_score_with_occlusion( - neighbor_surface, neighbor_position, canonical.selected_light_index, canonical.selected_uv, prev_acc_struct, debug_len); + neighbor_surface, neighbor_position, canonical.selected_light_index, canonical.selected_uv, prev_acc_struct, debug_len, 0xFF0000u); let r_canonical = ratio(canonical.history * canonical.selected_target_score * inv_count, neighbor_history * t_canonical_at_neighbor.score); mis_canonical += mis_scale * r_canonical; } let t_neighbor_at_canonical = estimate_target_score_with_occlusion( - surface, position, neighbor.light_index, neighbor.light_uv, acc_struct, debug_len); + surface, position, neighbor.light_index, neighbor.light_uv, acc_struct, debug_len, 0x0000FFu); let r_neighbor = ratio(neighbor_history * neighbor.target_score, canonical.history * t_neighbor_at_canonical.score * inv_count); let mis_neighbor = mis_scale * r_neighbor; @@ -473,8 +450,8 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr