From 72b8397e9d408b4645661c69804dd57b090b6b16 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Tue, 2 Aug 2022 18:55:25 -0700 Subject: [PATCH] Finished getting rid of the LightPath struct. Also misc cleanup of related code. --- src/renderer.rs | 466 ++++++++---------------------------------------- src/tracer.rs | 4 +- 2 files changed, 76 insertions(+), 394 deletions(-) diff --git a/src/renderer.rs b/src/renderer.rs index efb7b5e..db654b6 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -334,25 +334,31 @@ fn trace_camera_light_path( const BOUNCE_COUNT: usize = 3; let mut ray = camera_ray; - let mut ray_pdf = 1.0; // PDF from generating the ray. + let mut ray_pdf = 1.0; // PDF from generating the camera/bounce ray. let mut acc_color = Float4::splat(0.0); // Accumulated color. let mut attenuation = Float4::splat(1.0); // Color attenuation along the path so far. let mut sampling_seed = seed + 1; - for _ in 0..BOUNCE_COUNT { - let isect = tracer.trace(&mut ray); + for bounce in 0..BOUNCE_COUNT { + let isect = tracer.trace(ray); + if let SurfaceIntersection::Hit { intersection_data: idata, closure, } = &isect { - // Hit something! Do the stuff - // If it's an emission closure, handle specially: // - Collect light from the emission. // - Terminate the path. if let SurfaceClosure::Emit(color) = *closure { - let mis_pdf = power_heuristic(ray_pdf, idata.sample_pdf); + let mis_pdf = if bounce == 0 { + // The camera doesn't do light sampling, so there's + // no valid light sampling pdf to do MIS with. + 1.0 + } else { + power_heuristic(ray_pdf, idata.sample_pdf) + }; + acc_color += color.to_spectral_sample(ray.wavelength).e * attenuation / mis_pdf; break; @@ -377,85 +383,69 @@ fn trace_camera_light_path( &isect, ); if !light_info.is_none() && light_info.pdf() > 0.0 && light_info.selection_pdf() > 0.0 { - let light_pdf = light_info.pdf(); - let light_sel_pdf = light_info.selection_pdf(); - - // Calculate the shadow ray and surface closure stuff. - let (light_attenuation, closure_pdf, mut shadow_ray) = match light_info { + let shadow_ray = match light_info { SceneLightSample::None => unreachable!(), // Distant light SceneLightSample::Distant { direction, .. } => { - let (light_attenuation, closure_pdf) = closure.evaluate( - ray.dir, + let offset_pos = robust_ray_origin( + idata.pos, + idata.pos_err, + idata.nor_g.normalized(), direction, - idata.nor, - idata.nor_g, - ray.wavelength, ); - let shadow_ray = { - // Calculate the shadow ray for testing if the light is - // in shadow or not. - let offset_pos = robust_ray_origin( - idata.pos, - idata.pos_err, - idata.nor_g.normalized(), - direction, - ); - Ray::new( - offset_pos, - direction, - ray.time, - ray.wavelength, - std::f32::INFINITY, - true, - ) - }; - (light_attenuation, closure_pdf, shadow_ray) + Ray::new( + offset_pos, + direction, + ray.time, + ray.wavelength, + std::f32::INFINITY, + true, + ) } // Surface light SceneLightSample::Surface { sample_geo, .. } => { let dir = sample_geo.0 - idata.pos; - let (light_attenuation, closure_pdf) = - closure.evaluate(ray.dir, dir, idata.nor, idata.nor_g, ray.wavelength); - let shadow_ray = { - // Calculate the shadow ray for testing if the light is - // in shadow or not. - let offset_pos = robust_ray_origin( - idata.pos, - idata.pos_err, - idata.nor_g.normalized(), - dir, - ); - let offset_end = robust_ray_origin( - sample_geo.0, - sample_geo.2, - sample_geo.1.normalized(), - -dir, - ); - Ray::new( - offset_pos, - offset_end - offset_pos, - ray.time, - ray.wavelength, - 1.0, - true, - ) - }; - (light_attenuation, closure_pdf, shadow_ray) + let offset_pos = robust_ray_origin( + idata.pos, + idata.pos_err, + idata.nor_g.normalized(), + dir, + ); + let offset_end = robust_ray_origin( + sample_geo.0, + sample_geo.2, + sample_geo.1.normalized(), + -dir, + ); + Ray::new( + offset_pos, + offset_end - offset_pos, + ray.time, + ray.wavelength, + 1.0, + true, + ) } }; + let (closure_attenuation, closure_pdf) = closure.evaluate( + ray.dir, + shadow_ray.dir, + idata.nor, + idata.nor_g, + ray.wavelength, + ); + // If there's any possible contribution, shoot a shadow // ray to see if we can reach it. - if light_attenuation.e.max_element() > 0.0 { - if let SurfaceIntersection::Occlude = tracer.trace(&mut shadow_ray) { - // Calculate and store the light that will be contributed - // to the film plane if the light is not in shadow. - let light_mis_pdf = power_heuristic(light_pdf, closure_pdf); - acc_color += light_info.color().e * light_attenuation.e * attenuation - / (light_mis_pdf * light_sel_pdf); + if closure_attenuation.e.max_element() > 0.0 { + if let SurfaceIntersection::Miss = tracer.trace(shadow_ray) { + // Calculate the light contribution. + let light_mis_pdf = power_heuristic(light_info.pdf(), closure_pdf); + acc_color += light_info.color().e * closure_attenuation.e * attenuation + / (light_mis_pdf * light_info.selection_pdf()); } } } @@ -480,7 +470,7 @@ fn trace_camera_light_path( // Check if pdf is zero, to avoid NaN's. if (pdf > 0.0) && (filter.e.max_element() > 0.0) { // Account for the additional light attenuation from - // this bounce + // this bounce. attenuation *= filter.e; ray_pdf = pdf; @@ -498,334 +488,26 @@ fn trace_camera_light_path( } else { break; } + } else { + // Ray missed. Add sky color. + + // TODO: once sky sampling is a thing, do MIS here. + // let mis_pdf = power_heuristic(ray_pdf, idata.sample_pdf); + let mis_pdf = ray_pdf; + acc_color += scene + .world + .background_color + .to_spectral_sample(ray.wavelength) + .e + * mis_pdf; + + break; } } SpectralSample::from_parts(acc_color, ray.wavelength) } -// #[derive(Debug)] -// enum LightPathEvent { -// CameraRay, -// BounceRay, -// ShadowRay, -// } - -// #[derive(Debug)] -// pub struct LightPath { -// event: LightPathEvent, -// bounce_count: u32, - -// sampling_seed: u32, -// pixel_co: (u32, u32), -// sample_number: u32, // Which sample in the LDS sequence this is. -// dim_offset: u32, -// time: f32, -// wavelength: f32, - -// next_bounce_ray: Option, -// next_attenuation_fac: Float4, - -// closure_sample_pdf: f32, -// light_attenuation: Float4, -// pending_color_addition: Float4, -// color: Float4, -// } - -// #[allow(clippy::new_ret_no_self)] -// impl LightPath { -// fn new( -// scene: &Scene, -// sampling_seed: u32, -// pixel_co: (u32, u32), -// image_plane_co: (f32, f32), -// lens_uv: (f32, f32), -// time: f32, -// wavelength: f32, -// sample_number: u32, -// ) -> (LightPath, Ray) { -// ( -// LightPath { -// event: LightPathEvent::CameraRay, -// bounce_count: 0, - -// sampling_seed: sampling_seed ^ 0x40d4682b, -// pixel_co: pixel_co, -// sample_number: sample_number, -// dim_offset: 0, -// time: time, -// wavelength: wavelength, - -// next_bounce_ray: None, -// next_attenuation_fac: Float4::splat(1.0), - -// closure_sample_pdf: 1.0, -// light_attenuation: Float4::splat(1.0), -// pending_color_addition: Float4::splat(0.0), -// color: Float4::splat(0.0), -// }, -// scene.camera.generate_ray( -// image_plane_co.0, -// image_plane_co.1, -// time, -// wavelength, -// lens_uv.0, -// lens_uv.1, -// ), -// ) -// } - -// fn next_lds_sequence(&mut self) { -// self.dim_offset = 0; -// self.sampling_seed += 1; -// } - -// fn next_lds_samp(&mut self) -> (f32, f32, f32, f32) { -// let dimension = self.dim_offset; -// self.dim_offset += 1; -// get_sample_4d(self.sample_number, dimension, self.sampling_seed) -// } - -// fn next(&mut self, scene: &Scene, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool { -// match self.event { -// //-------------------------------------------------------------------- -// // Result of Camera or bounce ray, prepare next bounce and light rays -// LightPathEvent::CameraRay | LightPathEvent::BounceRay => { -// if let surface::SurfaceIntersection::Hit { -// intersection_data: ref idata, -// ref closure, -// } = *isect -// { -// // Hit something! Do the stuff - -// // If it's an emission closure, handle specially: -// // - Collect light from the emission. -// // - Terminate the path. -// use crate::shading::surface_closure::SurfaceClosure; -// if let SurfaceClosure::Emit(color) = *closure { -// let color = color.to_spectral_sample(self.wavelength).e; -// if let LightPathEvent::CameraRay = self.event { -// self.color += color; -// } else { -// let mis_pdf = -// power_heuristic(self.closure_sample_pdf, idata.sample_pdf); -// self.color += color * self.light_attenuation / mis_pdf; -// }; - -// return false; -// } - -// // Roll the previous closure pdf into the attenauation -// self.light_attenuation /= self.closure_sample_pdf; - -// // Prepare light ray -// self.next_lds_sequence(); -// let (light_n, d2, d3, d4) = self.next_lds_samp(); -// let light_uvw = (d2, d3, d4); -// let light_info = scene.sample_lights( -// light_n, -// light_uvw, -// self.wavelength, -// self.time, -// &XformFull::identity(), -// isect, -// ); -// let found_light = if light_info.is_none() -// || light_info.pdf() <= 0.0 -// || light_info.selection_pdf() <= 0.0 -// { -// false -// } else { -// let light_pdf = light_info.pdf(); -// let light_sel_pdf = light_info.selection_pdf(); - -// // Calculate the shadow ray and surface closure stuff -// let (attenuation, closure_pdf, shadow_ray) = match light_info { -// SceneLightSample::None => unreachable!(), - -// // Distant light -// SceneLightSample::Distant { direction, .. } => { -// let (attenuation, closure_pdf) = closure.evaluate( -// ray.dir, -// direction, -// idata.nor, -// idata.nor_g, -// self.wavelength, -// ); -// let shadow_ray = { -// // Calculate the shadow ray for testing if the light is -// // in shadow or not. -// let offset_pos = robust_ray_origin( -// idata.pos, -// idata.pos_err, -// idata.nor_g.normalized(), -// direction, -// ); -// Ray::new( -// offset_pos, -// direction, -// self.time, -// self.wavelength, -// std::f32::INFINITY, -// true, -// ) -// }; -// (attenuation, closure_pdf, shadow_ray) -// } - -// // Surface light -// SceneLightSample::Surface { sample_geo, .. } => { -// let dir = sample_geo.0 - idata.pos; -// let (attenuation, closure_pdf) = closure.evaluate( -// ray.dir, -// dir, -// idata.nor, -// idata.nor_g, -// self.wavelength, -// ); -// let shadow_ray = { -// // Calculate the shadow ray for testing if the light is -// // in shadow or not. -// let offset_pos = robust_ray_origin( -// idata.pos, -// idata.pos_err, -// idata.nor_g.normalized(), -// dir, -// ); -// let offset_end = robust_ray_origin( -// sample_geo.0, -// sample_geo.2, -// sample_geo.1.normalized(), -// -dir, -// ); -// Ray::new( -// offset_pos, -// offset_end - offset_pos, -// self.time, -// self.wavelength, -// 1.0, -// true, -// ) -// }; -// (attenuation, closure_pdf, shadow_ray) -// } -// }; - -// // If there's any possible contribution, set up for a -// // light ray. -// if attenuation.e.max_element() <= 0.0 { -// false -// } else { -// // Calculate and store the light that will be contributed -// // to the film plane if the light is not in shadow. -// let light_mis_pdf = power_heuristic(light_pdf, closure_pdf); -// self.pending_color_addition = -// light_info.color().e * attenuation.e * self.light_attenuation -// / (light_mis_pdf * light_sel_pdf); - -// *ray = shadow_ray; - -// true -// } -// }; - -// // Prepare bounce ray -// let do_bounce = if self.bounce_count < 2 { -// self.bounce_count += 1; - -// // Sample closure -// let (dir, filter, pdf) = { -// self.next_lds_sequence(); -// let (u, v, _, _) = self.next_lds_samp(); -// closure.sample( -// idata.incoming, -// idata.nor, -// idata.nor_g, -// (u, v), -// self.wavelength, -// ) -// }; - -// // Check if pdf is zero, to avoid NaN's. -// if (pdf > 0.0) && (filter.e.max_element() > 0.0) { -// // Account for the additional light attenuation from -// // this bounce -// self.next_attenuation_fac = filter.e; -// self.closure_sample_pdf = pdf; - -// // Calculate the ray for this bounce -// let offset_pos = robust_ray_origin( -// idata.pos, -// idata.pos_err, -// idata.nor_g.normalized(), -// dir, -// ); -// self.next_bounce_ray = Some(Ray::new( -// offset_pos, -// dir, -// self.time, -// self.wavelength, -// std::f32::INFINITY, -// false, -// )); - -// true -// } else { -// false -// } -// } else { -// self.next_bounce_ray = None; -// false -// }; - -// // Book keeping for next event -// if found_light { -// self.event = LightPathEvent::ShadowRay; -// return true; -// } else if do_bounce { -// *ray = self.next_bounce_ray.unwrap(); -// self.event = LightPathEvent::BounceRay; -// self.light_attenuation *= self.next_attenuation_fac; -// return true; -// } else { -// return false; -// } -// } else { -// // Didn't hit anything, so background color -// self.color += scene -// .world -// .background_color -// .to_spectral_sample(self.wavelength) -// .e -// * self.light_attenuation -// / self.closure_sample_pdf; -// return false; -// } -// } - -// //-------------------------------------------------------------------- -// // Result of shadow ray from sampling a light -// LightPathEvent::ShadowRay => { -// // If the light was not in shadow, add it's light to the film -// // plane. -// if let surface::SurfaceIntersection::Miss = *isect { -// self.color += self.pending_color_addition; -// } - -// // Set up for the next bounce, if any -// if let Some(ref nbr) = self.next_bounce_ray { -// *ray = *nbr; -// self.light_attenuation *= self.next_attenuation_fac; -// self.event = LightPathEvent::BounceRay; -// return true; -// } else { -// return false; -// } -// } -// } -// } -// } - /// Gets a sample, using LDS samples for lower dimensions, /// and switching to random samples at higher dimensions where /// LDS samples aren't available. diff --git a/src/tracer.rs b/src/tracer.rs index 123ad4d..a453f4a 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -25,14 +25,14 @@ impl<'a> Tracer<'a> { self.ray_trace_count } - pub fn trace(&mut self, ray: &mut Ray) -> SurfaceIntersection { + pub fn trace(&mut self, mut ray: Ray) -> SurfaceIntersection { self.ray_trace_count += 1; let local_ray = ray.to_local(); let space = XformFull::identity(); let mut isect = SurfaceIntersection::Miss; - self.trace_assembly(self.root, ray, &local_ray, &space, &mut isect); + self.trace_assembly(self.root, &mut ray, &local_ray, &space, &mut isect); isect }