diff --git a/Cargo.toml b/Cargo.toml index 03c104ca..5b6c236c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,10 @@ glam = { version = "0.27", features = ["mint"] } gltf = { version = "1.1", default-features = false } log = "0.4" mint = "0.5" -naga = { version = "22", features = ["wgsl-in"] } +#TODO: switch to crates once https://github.com/gfx-rs/wgpu/pull/6256 is published +naga = { git = "https://github.com/gfx-rs/wgpu", rev = "dfc384a7fd4ab7250a75d59c6f831d9ffb220f7e", features = [ + "wgsl-in", +] } profiling = "1" slab = "0.4" strum = { version = "0.25", features = ["derive"] } diff --git a/blade-graphics/src/vulkan/pipeline.rs b/blade-graphics/src/vulkan/pipeline.rs index 775ee1cf..24ac5f7c 100644 --- a/blade-graphics/src/vulkan/pipeline.rs +++ b/blade-graphics/src/vulkan/pipeline.rs @@ -96,6 +96,8 @@ impl super::Context { naga_options_debug.debug_info = Some(naga::back::spv::DebugInfo { source_code: &sf.shader.source, file_name: &file_path, + //TODO: switch to WGSL once NSight Graphics recognizes it + language: naga::back::spv::SourceLanguage::GLSL, }); &naga_options_debug } else { diff --git a/blade-helpers/src/camera.rs b/blade-helpers/src/camera.rs index b866ca7f..246d014b 100644 --- a/blade-helpers/src/camera.rs +++ b/blade-helpers/src/camera.rs @@ -1,5 +1,7 @@ use super::ExposeHud; +const MAX_FLY_SPEED: f32 = 1000000.0; + pub struct ControlledCamera { pub inner: blade_render::Camera, pub fly_speed: f32, @@ -86,6 +88,14 @@ impl ControlledCamera { true } + + pub fn on_wheel(&mut self, delta: winit::event::MouseScrollDelta) { + let shift = match delta { + winit::event::MouseScrollDelta::LineDelta(_, lines) => lines, + winit::event::MouseScrollDelta::PixelDelta(position) => position.y as f32, + }; + self.fly_speed = (self.fly_speed * shift.exp()).clamp(1.0, MAX_FLY_SPEED); + } } impl ExposeHud for ControlledCamera { @@ -105,7 +115,7 @@ impl ExposeHud for ControlledCamera { }); ui.add(egui::Slider::new(&mut self.inner.fov_y, 0.5f32..=2.0f32).text("FOV")); ui.add( - egui::Slider::new(&mut self.fly_speed, 1f32..=100000f32) + egui::Slider::new(&mut self.fly_speed, 1f32..=MAX_FLY_SPEED) .text("Fly speed") .logarithmic(true), ); diff --git a/blade-helpers/src/hud.rs b/blade-helpers/src/hud.rs index a135a337..880b745f 100644 --- a/blade-helpers/src/hud.rs +++ b/blade-helpers/src/hud.rs @@ -31,6 +31,10 @@ impl ExposeHud for blade_render::RayConfig { .text("T min") .logarithmic(true), ); + ui.checkbox(&mut self.pairwise_mis, "Pairwise MIS"); + ui.add( + egui::widgets::Slider::new(&mut self.defensive_mis, 0.0..=1.0).text("Defensive MIS"), + ); } } diff --git a/blade-render/code/blur.wgsl b/blade-render/code/a-trous.wgsl similarity index 100% rename from blade-render/code/blur.wgsl rename to blade-render/code/a-trous.wgsl diff --git a/blade-render/code/camera.inc.wgsl b/blade-render/code/camera.inc.wgsl index bffbbbd9..c7132373 100644 --- a/blade-render/code/camera.inc.wgsl +++ b/blade-render/code/camera.inc.wgsl @@ -6,11 +6,13 @@ struct CameraParams { target_size: vec2, } +const VFLIP: vec2 = vec2(1.0, -1.0); + fn get_ray_direction(cp: CameraParams, pixel: vec2) -> vec3 { let half_size = 0.5 * vec2(cp.target_size); let ndc = (vec2(pixel) + vec2(0.5) - half_size) / half_size; // Right-handed coordinate system with X=right, Y=up, and Z=towards the camera - let local_dir = vec3(ndc * tan(0.5 * cp.fov), -1.0); + let local_dir = vec3(VFLIP * ndc * tan(0.5 * cp.fov), -1.0); return normalize(qrot(cp.orientation, local_dir)); } @@ -21,7 +23,7 @@ fn get_projected_pixel_float(cp: CameraParams, point: vec3) -> vec2 { } let ndc = local_dir.xy / (-local_dir.z * tan(0.5 * cp.fov)); let half_size = 0.5 * vec2(cp.target_size); - return (ndc + vec2(1.0)) * half_size; + return (VFLIP * ndc + vec2(1.0)) * half_size; } fn get_projected_pixel(cp: CameraParams, point: vec3) -> vec2 { diff --git a/blade-render/code/fill-gbuf.wgsl b/blade-render/code/fill-gbuf.wgsl index 346edf51..becba21e 100644 --- a/blade-render/code/fill-gbuf.wgsl +++ b/blade-render/code/fill-gbuf.wgsl @@ -68,6 +68,9 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { if (any(global_id.xy >= camera.target_size)) { return; } + if (WRITE_DEBUG_IMAGE && debug.view_mode != DebugMode_Final) { + textureStore(out_debug, global_id.xy, vec4(0.0)); + } var rq: ray_query; let ray_dir = get_ray_direction(camera, vec2(global_id.xy)); @@ -170,7 +173,7 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { albedo = (base_color_factor * base_color_sample).xyz; } - if (debug.view_mode == DebugMode_HitConsistency) { + if (WRITE_DEBUG_IMAGE && debug.view_mode == DebugMode_HitConsistency) { let reprojected = get_projected_pixel(camera, hit_position); let barycentrics_pos_diff = (intersection.object_to_world * position_object).xyz - hit_position; let camera_projection_diff = vec2(global_id.xy) - vec2(reprojected); @@ -183,16 +186,13 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { //TODO: consider just storing integers here? //TODO: technically this "0.5" is just a waste compute on both packing and unpacking motion = prev_screen - vec2(global_id.xy) - 0.5; - if (debug.view_mode == DebugMode_Motion) { + if (WRITE_DEBUG_IMAGE && debug.view_mode == DebugMode_Motion) { textureStore(out_debug, global_id.xy, vec4(motion * MOTION_SCALE + vec2(0.5), 0.0, 1.0)); } } else { if (enable_debug) { debug_buf.entry = DebugEntry(); } - if (debug.view_mode != DebugMode_Final) { - textureStore(out_debug, global_id.xy, vec4(0.0)); - } } // TODO: option to avoid writing data for the sky diff --git a/blade-render/code/gbuf.inc.wgsl b/blade-render/code/gbuf.inc.wgsl index ecb4642d..374aa708 100644 --- a/blade-render/code/gbuf.inc.wgsl +++ b/blade-render/code/gbuf.inc.wgsl @@ -1,2 +1,3 @@ const MOTION_SCALE: f32 = 0.02; -const USE_MOTION_VECTORS: bool = true; \ No newline at end of file +const USE_MOTION_VECTORS: bool = true; +const WRITE_DEBUG_IMAGE: bool = false; diff --git a/blade-render/code/post-proc.wgsl b/blade-render/code/post-proc.wgsl index ac6edd2f..dc708bb3 100644 --- a/blade-render/code/post-proc.wgsl +++ b/blade-render/code/post-proc.wgsl @@ -21,7 +21,7 @@ struct VertexOutput { } @vertex -fn blit_vs(@builtin(vertex_index) vi: u32) -> VertexOutput { +fn postfx_vs(@builtin(vertex_index) vi: u32) -> VertexOutput { var vo: VertexOutput; vo.clip_pos = vec4(f32(vi & 1u) * 4.0 - 1.0, f32(vi & 2u) * 2.0 - 1.0, 0.0, 1.0); vo.input_size = textureDimensions(light_diffuse, 0); @@ -29,8 +29,8 @@ fn blit_vs(@builtin(vertex_index) vi: u32) -> VertexOutput { } @fragment -fn blit_fs(vo: VertexOutput) -> @location(0) vec4 { - let tc = vec2(i32(vo.clip_pos.x), i32(vo.input_size.y) - i32(vo.clip_pos.y) - 1); +fn postfx_fs(vo: VertexOutput) -> @location(0) vec4 { + let tc = vec2(i32(vo.clip_pos.x), i32(vo.clip_pos.y)); let illumunation = textureLoad(light_diffuse, tc, 0); if (debug_params.view_mode == DebugMode_Final) { let albedo = textureLoad(t_albedo, tc, 0).xyz; diff --git a/blade-render/code/ray-trace.wgsl b/blade-render/code/ray-trace.wgsl index b27b1065..a51931ef 100644 --- a/blade-render/code/ray-trace.wgsl +++ b/blade-render/code/ray-trace.wgsl @@ -12,16 +12,16 @@ const RAY_FLAG_CULL_NO_OPAQUE: u32 = 0x80u; const PI: f32 = 3.1415926; const MAX_RESERVOIRS: u32 = 2u; -// See "9.1 pairwise mis for robust reservoir reuse" -// "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; +const DECOUPLED_SHADING: bool = true; + +// 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; struct MainParams { frame_index: u32, @@ -33,6 +33,8 @@ struct MainParams { spatial_tap_history: u32, spatial_radius: i32, t_start: f32, + use_pairwise_mis: u32, + defensive_mis: f32, use_motion_vectors: u32, }; @@ -320,17 +322,8 @@ fn evaluate_sample(ls: LightSample, surface: Surface, start_pos: vec3, debu return brdf; } -struct HeuristicFactors { - weight: f32, - //history: f32, -} - -fn balance_heuristic(w0: f32, w1: f32, h0: f32, h1: f32) -> HeuristicFactors { - var hf: HeuristicFactors; - let balance_denom = h0 * w0 + h1 * w1; - hf.weight = select(h0 * w0 / balance_denom, 0.0, balance_denom <= 0.0); - //hf.history = select(pow(clamp(w1 / w0, 0.0, 1.0), 8.0), 1.0, w0 <= 0.0); - return hf; +fn ratio(a: f32, b: f32) -> f32 { + return select(0.0, a / (a+b), a+b > 0.0); } struct RestirOutput { @@ -338,9 +331,6 @@ struct RestirOutput { } fn compute_restir(surface: Surface, pixel: vec2, rng: ptr, enable_debug: bool) -> RestirOutput { - if (debug.view_mode == DebugMode_Depth) { - textureStore(out_debug, pixel, vec4(surface.depth / camera.depth)); - } let ray_dir = get_ray_direction(camera, pixel); let pixel_index = get_reservoir_index(pixel, camera); if (surface.depth == 0.0) { @@ -349,10 +339,13 @@ 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)); - if (debug.view_mode == DebugMode_Normal) { + if (WRITE_DEBUG_IMAGE && debug.view_mode == DebugMode_Normal) { textureStore(out_debug, pixel, vec4(normal, 0.0)); } @@ -374,22 +367,36 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(get_prev_pixel(pixel, position)); + let center_coord = get_prev_pixel(pixel, position); + let center_pixel = vec2(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; - for (var tap = 0u; tap <= parameters.spatial_taps; tap += 1u) { - var other_pixel = prev_pixel; - if (tap != 0u) { - let r0 = max(prev_pixel - vec2(parameters.spatial_radius), vec2(0)); - let r1 = min(prev_pixel + vec2(parameters.spatial_radius + 1), vec2(prev_camera.target_size)); + 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); + + 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)))); - } else if (parameters.temporal_tap == 0u) - { - continue; + let diff = other_pixel - center_pixel; + if (dot(diff, diff) < MIN_SPATIAL_REUSE_DISTANCE) { + continue; + } } let other_index = get_reservoir_index(other_pixel, prev_camera); @@ -407,27 +414,35 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr(0.0); + for (var i = 0u; i < min(3u, accepted_count); i += 1u) { + color[i] = 1.0; } + textureStore(out_debug, pixel, color); } // Next, evaluate the MIS of each of the samples versus the canonical one. var reservoir = LiveReservoir(); - var shaded_color = vec3(0.0); - var mis_canonical = BASE_CANONICAL_MIS; var color_and_weight = vec4(0.0); + let mis_scale = 1.0 / (f32(accepted_count) + parameters.defensive_mis); + var mis_canonical = select(mis_scale * parameters.defensive_mis, 1.0, accepted_count == 0u || parameters.use_pairwise_mis == 0u); + let inv_count = 1.0 / f32(accepted_count); + 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; - if (PAIRWISE_MIS) { + 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)); { // scoping this to hint the register allocation @@ -437,30 +452,20 @@ fn compute_restir(surface: Surface, pixel: vec2, rng: ptr, 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 effective_history = select(reservoir.history, 1.0, parameters.use_pairwise_mis != 0); let stored = pack_reservoir_detail(reservoir, effective_history); reservoirs[pixel_index] = stored; var ro = RestirOutput(); diff --git a/blade-render/src/render/mod.rs b/blade-render/src/render/mod.rs index c856538f..2d61582f 100644 --- a/blade-render/src/render/mod.rs +++ b/blade-render/src/render/mod.rs @@ -51,7 +51,8 @@ pub enum DebugMode { Normal = 2, Motion = 3, HitConsistency = 4, - Variance = 5, + SampleReuse = 5, + Variance = 10, } impl Default for DebugMode { @@ -95,6 +96,12 @@ pub struct RayConfig { pub spatial_tap_history: u32, pub spatial_radius: u32, pub t_start: f32, + /// Evaluate MIS factor for ReSTIR in a pair-wise fashion. + /// Adds 2 extra visibility rays per reused sample. + pub pairwise_mis: bool, + /// Defensive MIS factor for the canonical sample. + /// Can be between 0 and 1. + pub defensive_mis: f32, } #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] @@ -230,7 +237,7 @@ impl RestirTargets { Self { reservoir_buf, debug: RenderTarget::new( - "deubg", + "debug", blade_graphics::TextureFormat::Rgba8Unorm, size, encoder, @@ -292,7 +299,7 @@ impl RestirTargets { struct Blur { temporal_accum_pipeline: blade_graphics::ComputePipeline, - atrous_pipeline: blade_graphics::ComputePipeline, + a_trous_pipeline: blade_graphics::ComputePipeline, } /// Blade Renderer is a comprehensive rendering solution for @@ -327,6 +334,7 @@ pub struct Renderer { surface_info: blade_graphics::SurfaceInfo, frame_index: usize, frame_scene_built: usize, + is_frozen: bool, //TODO: refactor `ResourceArray` to not carry the freelist logic // This way we can embed user info into the allocator. texture_resource_lookup: @@ -365,6 +373,8 @@ struct MainParams { spatial_tap_history: u32, spatial_radius: u32, t_start: f32, + use_pairwise_mis: u32, + defensive_mis: f32, use_motion_vectors: u32, } @@ -440,7 +450,7 @@ struct TemporalAccumData { } #[derive(blade_macros::ShaderData)] -struct AtrousData { +struct ATrousData { params: BlurParams, input: blade_graphics::TextureView, t_depth: blade_graphics::TextureView, @@ -489,7 +499,7 @@ pub struct Shaders { env_prepare: blade_asset::Handle, fill_gbuf: blade_asset::Handle, ray_trace: blade_asset::Handle, - blur: blade_asset::Handle, + a_trous: blade_asset::Handle, post_proc: blade_asset::Handle, debug_draw: blade_asset::Handle, debug_blit: blade_asset::Handle, @@ -502,7 +512,7 @@ impl Shaders { env_prepare: ctx.load_shader("env-prepare.wgsl"), fill_gbuf: ctx.load_shader("fill-gbuf.wgsl"), ray_trace: ctx.load_shader("ray-trace.wgsl"), - blur: ctx.load_shader("blur.wgsl"), + a_trous: ctx.load_shader("a-trous.wgsl"), post_proc: ctx.load_shader("post-proc.wgsl"), debug_draw: ctx.load_shader("debug-draw.wgsl"), debug_blit: ctx.load_shader("debug-blit.wgsl"), @@ -515,7 +525,7 @@ struct ShaderPipelines { fill: blade_graphics::ComputePipeline, main: blade_graphics::ComputePipeline, temporal_accum: blade_graphics::ComputePipeline, - atrous: blade_graphics::ComputePipeline, + a_trous: blade_graphics::ComputePipeline, post_proc: blade_graphics::RenderPipeline, env_prepare: blade_graphics::ComputePipeline, reservoir_size: u32, @@ -564,13 +574,13 @@ impl ShaderPipelines { }) } - fn create_atrous( + fn create_a_trous( shader: &blade_graphics::Shader, gpu: &blade_graphics::Context, ) -> blade_graphics::ComputePipeline { - let layout = ::layout(); + let layout = ::layout(); gpu.create_compute_pipeline(blade_graphics::ComputePipelineDesc { - name: "atrous", + name: "a-trous", data_layouts: &[&layout], compute: shader.at("atrous3x3"), }) @@ -589,9 +599,9 @@ impl ShaderPipelines { topology: blade_graphics::PrimitiveTopology::TriangleStrip, ..Default::default() }, - vertex: shader.at("blit_vs"), + vertex: shader.at("postfx_vs"), vertex_fetches: &[], - fragment: shader.at("blit_fs"), + fragment: shader.at("postfx_fs"), color_targets: &[info.format.into()], depth_stencil: None, }) @@ -604,12 +614,12 @@ impl ShaderPipelines { shader_man: &blade_asset::AssetManager, ) -> Result { let sh_main = shader_man[shaders.ray_trace].raw.as_ref().unwrap(); - let sh_blur = shader_man[shaders.blur].raw.as_ref().unwrap(); + let sh_a_trous = shader_man[shaders.a_trous].raw.as_ref().unwrap(); Ok(Self { fill: Self::create_gbuf_fill(shader_man[shaders.fill_gbuf].raw.as_ref().unwrap(), gpu), main: Self::create_ray_trace(sh_main, gpu), - temporal_accum: Self::create_temporal_accum(sh_blur, gpu), - atrous: Self::create_atrous(sh_blur, gpu), + temporal_accum: Self::create_temporal_accum(sh_a_trous, gpu), + a_trous: Self::create_a_trous(sh_a_trous, gpu), post_proc: Self::create_post_proc( shader_man[shaders.post_proc].raw.as_ref().unwrap(), config.surface_info, @@ -702,7 +712,7 @@ impl Renderer { post_proc_pipeline: sp.post_proc, blur: Blur { temporal_accum_pipeline: sp.temporal_accum, - atrous_pipeline: sp.atrous, + a_trous_pipeline: sp.a_trous, }, acceleration_structure: blade_graphics::AccelerationStructure::default(), prev_acceleration_structure: blade_graphics::AccelerationStructure::default(), @@ -719,6 +729,7 @@ impl Renderer { surface_info: config.surface_info, frame_index: 0, frame_scene_built: 0, + is_frozen: false, texture_resource_lookup: HashMap::default(), } } @@ -743,7 +754,7 @@ impl Renderer { gpu.destroy_sampler(self.samplers.linear); // pipelines gpu.destroy_compute_pipeline(&mut self.blur.temporal_accum_pipeline); - gpu.destroy_compute_pipeline(&mut self.blur.atrous_pipeline); + gpu.destroy_compute_pipeline(&mut self.blur.a_trous_pipeline); gpu.destroy_compute_pipeline(&mut self.fill_pipeline); gpu.destroy_compute_pipeline(&mut self.main_pipeline); gpu.destroy_render_pipeline(&mut self.post_proc_pipeline); @@ -761,7 +772,7 @@ impl Renderer { tasks.extend(asset_hub.shaders.hot_reload(&mut self.shaders.fill_gbuf)); tasks.extend(asset_hub.shaders.hot_reload(&mut self.shaders.ray_trace)); - tasks.extend(asset_hub.shaders.hot_reload(&mut self.shaders.blur)); + tasks.extend(asset_hub.shaders.hot_reload(&mut self.shaders.a_trous)); tasks.extend(asset_hub.shaders.hot_reload(&mut self.shaders.post_proc)); tasks.extend(asset_hub.shaders.hot_reload(&mut self.shaders.debug_draw)); tasks.extend(asset_hub.shaders.hot_reload(&mut self.shaders.debug_blit)); @@ -790,11 +801,11 @@ impl Renderer { self.main_pipeline = ShaderPipelines::create_ray_trace(shader, gpu); } } - if self.shaders.blur != old.blur { - if let Ok(ref shader) = asset_hub.shaders[self.shaders.blur].raw { + if self.shaders.a_trous != old.a_trous { + if let Ok(ref shader) = asset_hub.shaders[self.shaders.a_trous].raw { self.blur.temporal_accum_pipeline = ShaderPipelines::create_temporal_accum(shader, gpu); - self.blur.atrous_pipeline = ShaderPipelines::create_atrous(shader, gpu); + self.blur.a_trous_pipeline = ShaderPipelines::create_a_trous(shader, gpu); } } if self.shaders.post_proc != old.post_proc { @@ -1101,6 +1112,7 @@ impl Renderer { if !config.frozen { self.frame_index += 1; } + self.is_frozen = config.frozen; self.targets.camera_params[self.frame_index % 2] = self.make_camera_params(camera); self.post_proc_input_index = self.frame_index % 2; } @@ -1117,6 +1129,7 @@ impl Renderer { ) { let debug = self.make_debug_params(&debug_config); let (cur, prev) = self.work_indices(); + assert_eq!(cur, self.post_proc_input_index); if let mut pass = command_encoder.compute() { let mut pc = pass.with(&self.fill_pipeline); @@ -1165,7 +1178,9 @@ impl Renderer { spatial_tap_history: ray_config.spatial_tap_history, spatial_radius: ray_config.spatial_radius, t_start: ray_config.t_start, - use_motion_vectors: (self.frame_scene_built == self.frame_index) as u32, + use_pairwise_mis: ray_config.pairwise_mis as u32, + defensive_mis: ray_config.defensive_mis, + use_motion_vectors: (self.frame_scene_built >= self.frame_index) as u32, }, acc_struct: self.acceleration_structure, prev_acc_struct: if self.frame_scene_built < self.frame_index @@ -1209,7 +1224,7 @@ impl Renderer { extent: [self.surface_size.width, self.surface_size.height], temporal_weight: denoiser_config.temporal_weight, iteration: 0, - use_motion_vectors: (self.frame_scene_built == self.frame_index) as u32, + use_motion_vectors: (self.frame_scene_built >= self.frame_index) as u32, pad: 0, }; let (cur, prev) = self.work_indices(); @@ -1220,7 +1235,7 @@ impl Renderer { let mut pc = pass.with(&self.blur.temporal_accum_pipeline); let groups = self .blur - .atrous_pipeline + .a_trous_pipeline .get_dispatch_for(self.surface_size); pc.bind( 0, @@ -1243,17 +1258,18 @@ impl Renderer { self.targets.light_diffuse.views.swap(cur, temp); } - let mut ping_pong = [temp, prev]; + assert_eq!(cur, self.post_proc_input_index); + let mut ping_pong = [temp, if self.is_frozen { cur } else { prev }]; for _ in 0..denoiser_config.num_passes { let mut pass = command_encoder.compute(); - let mut pc = pass.with(&self.blur.atrous_pipeline); + let mut pc = pass.with(&self.blur.a_trous_pipeline); let groups = self .blur - .atrous_pipeline + .a_trous_pipeline .get_dispatch_for(self.surface_size); pc.bind( 0, - &AtrousData { + &ATrousData { params, input: self.targets.light_diffuse.views[self.post_proc_input_index], t_depth: self.targets.depth.views[cur], diff --git a/examples/scene/main.rs b/examples/scene/main.rs index f09de24a..7b34e4c7 100644 --- a/examples/scene/main.rs +++ b/examples/scene/main.rs @@ -263,9 +263,11 @@ impl Example { temporal_tap: true, temporal_history: 10, spatial_taps: 1, - spatial_tap_history: 5, + spatial_tap_history: 10, spatial_radius: 10, t_start: 0.1, + pairwise_mis: true, + defensive_mis: 0.0, }, denoiser_enabled: true, denoiser_config: blade_render::DenoiserConfig { @@ -945,6 +947,9 @@ fn main() { } last_mouse_pos = [position.x as i32, position.y as i32]; } + winit::event::WindowEvent::MouseWheel { delta, .. } => { + example.camera.on_wheel(delta); + } winit::event::WindowEvent::HoveredFile(_) => { example.is_file_hovered = true; example diff --git a/src/lib.rs b/src/lib.rs index 5c8320b6..2ee0b77a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -488,9 +488,11 @@ impl Engine { temporal_tap: true, temporal_history: 10, spatial_taps: 1, - spatial_tap_history: 5, + spatial_tap_history: 10, spatial_radius: 10, t_start: 0.01, + pairwise_mis: true, + defensive_mis: 0.1, }, denoiser_enabled: true, denoiser_config: blade_render::DenoiserConfig { @@ -605,14 +607,16 @@ impl Engine { } // Rebuilding every frame - self.renderer.build_scene( - command_encoder, - &self.render_objects, - self.environment_map, - &self.asset_hub, - &self.gpu_context, - temp, - ); + if !self.frame_config.frozen { + self.renderer.build_scene( + command_encoder, + &self.render_objects, + self.environment_map, + &self.asset_hub, + &self.gpu_context, + temp, + ); + } self.renderer.prepare( command_encoder,