From 380b1c9f901c6247c9f906d120e0a85c46792c4b Mon Sep 17 00:00:00 2001 From: pingu Date: Thu, 22 Jan 2026 16:18:26 +0000 Subject: [PATCH] Some more refactoring, more error correction. When will this end? --- src/integrators/base.rs | 71 +++ src/integrators/constants.rs | 42 ++ src/integrators/mod.rs | 1170 +--------------------------------- src/integrators/path.rs | 320 ++++++++++ src/integrators/pipeline.rs | 11 +- src/integrators/state.rs | 45 ++ 6 files changed, 492 insertions(+), 1167 deletions(-) create mode 100644 src/integrators/base.rs create mode 100644 src/integrators/constants.rs create mode 100644 src/integrators/path.rs create mode 100644 src/integrators/state.rs diff --git a/src/integrators/base.rs b/src/integrators/base.rs new file mode 100644 index 0000000..c0687a4 --- /dev/null +++ b/src/integrators/base.rs @@ -0,0 +1,71 @@ +use super::state::PathState; +use crate::core::light::Light; +use shared::core::geometry::Ray; +use shared::core::interaction::{Interaction, InteractionTrait}; +use shared::core::primitive::Primitive; +use shared::core::shape::ShapeIntersection; +use shared::lights::LightSampler; +use shared::spectra::SampledWavelengths; +use shared::utils::sampling::power_heuristic; +use shared::{Float, SHADOW_EPSILON}; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub struct IntegratorBase { + pub aggregate: Arc, + pub lights: Vec>, + pub infinite_lights: Vec>, +} + +impl IntegratorBase { + pub fn new(aggregate: Arc, lights: Vec>) -> Self { + let infinite_lights = lights + .iter() + .filter(|light| light.light_type().is_infinite()) + .cloned() + .collect(); + + Self { + aggregate, + lights, + infinite_lights, + } + } + + pub fn intersect(&self, ray: &Ray, t_max: Option) -> Option { + self.aggregate.intersect(ray, t_max) + } + + pub fn intersect_p(&self, ray: &Ray, t_max: Option) -> bool { + self.aggregate.intersect_p(ray, t_max) + } + + pub fn unoccluded(&self, p0: &Interaction, p1: &Interaction) -> bool { + !self.intersect_p(&p0.spawn_ray_to_interaction(p1), Some(1. - SHADOW_EPSILON)) + } + + pub fn add_infinite_light_contribution( + &self, + state: &mut PathState, + ray: &Ray, + lambda: &SampledWavelengths, + light_sampler: Option<&LightSampler>, + use_mis: bool, + ) { + for light in &self.infinite_lights { + let le = light.le(ray, lambda); + if le.is_black() { + continue; + } + + if state.depth == 0 || state.specular_bounce || !use_mis { + state.l += state.beta * le; + } else if let Some(sampler) = light_sampler { + let p_l = sampler.pmf_with_context(&state.prev_ctx, light) + * light.pdf_li(&state.prev_ctx, ray.d, true); + let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); + state.l += state.beta * w_b * le; + } + } + } +} diff --git a/src/integrators/constants.rs b/src/integrators/constants.rs new file mode 100644 index 0000000..67f66d8 --- /dev/null +++ b/src/integrators/constants.rs @@ -0,0 +1,42 @@ +use shared::core::geometry::Point2f; +use shared::Float; + +pub const N_RHO_SAMPLES: usize = 16; + +pub static UC_RHO: [Float; N_RHO_SAMPLES] = [ + 0.75741637, + 0.37870818, + 0.7083487, + 0.18935409, + 0.9149363, + 0.35417435, + 0.5990858, + 0.09467703, + 0.8578725, + 0.45746812, + 0.686759, + 0.17708716, + 0.9674518, + 0.2995429, + 0.5083201, + 0.047338516, +]; + +pub static U_RHO: [Point2f; N_RHO_SAMPLES] = [ + Point2f::new(0.855985, 0.570367), + Point2f::new(0.381823, 0.851844), + Point2f::new(0.285328, 0.764262), + Point2f::new(0.733380, 0.114073), + Point2f::new(0.542663, 0.344465), + Point2f::new(0.127274, 0.414848), + Point2f::new(0.964700, 0.947162), + Point2f::new(0.594089, 0.643463), + Point2f::new(0.095109, 0.170369), + Point2f::new(0.825444, 0.263359), + Point2f::new(0.429467, 0.454469), + Point2f::new(0.244460, 0.816459), + Point2f::new(0.756135, 0.731258), + Point2f::new(0.516165, 0.152852), + Point2f::new(0.180888, 0.214174), + Point2f::new(0.898579, 0.503897), +]; diff --git a/src/integrators/mod.rs b/src/integrators/mod.rs index cdd2297..3594fd7 100644 --- a/src/integrators/mod.rs +++ b/src/integrators/mod.rs @@ -1,71 +1,10 @@ +mod base; +mod constants; +mod path; mod pipeline; +mod state; -use shared::core::bsdf::BSDF; -use shared::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction}; -use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode}; -use shared::core::camera::Camera; -use shared::core::film::VisibleSurface; -use shared::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike}; -use shared::core::interaction::{ - Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction, -}; -use shared::core::light::{Light, LightSampleContext}; -use shared::core::medium::{MediumTrait, PhaseFunctionTrait}; -use shared::core::options::get_options; -use shared::core::pbrt::{Float, SHADOW_EPSILON}; -use shared::core::primitive::{Primitive, PrimitiveTrait}; -use shared::core::sampler::{Sampler, SamplerTrait}; -use shared::core::shape::ShapeIntersection; -use shared::lights::sampler::LightSamplerTrait; -use shared::lights::sampler::{LightSampler, UniformLightSampler}; -use shared::spectra::{SampledSpectrum, SampledWavelengths}; -use shared::utils::hash::{hash_buffer, mix_bits}; -use shared::utils::math::{float_to_bits, sample_discrete, square}; -use shared::utils::rng::Rng; -use shared::utils::sampling::{ - WeightedReservoirSampler, power_heuristic, sample_uniform_hemisphere, sample_uniform_sphere, - uniform_hemisphere_pdf, uniform_sphere_pdf, -}; - -use std::sync::Arc; - -use crate::Arena; -use crate::spectra::get_spectra_context; - -#[derive(Clone, Debug)] -pub struct IntegratorBase { - pub aggregate: Arc, - pub lights: Vec>, - pub infinite_lights: Vec>, -} - -impl IntegratorBase { - pub fn new(aggregate: Arc, lights: Vec>) -> Self { - let infinite_lights = lights - .iter() - .filter(|light| light.light_type().is_infinite()) - .cloned() - .collect(); - - Self { - aggregate, - lights, - infinite_lights, - } - } - - pub fn intersect(&self, ray: &Ray, t_max: Option) -> Option { - self.aggregate.intersect(ray, t_max) - } - - pub fn intersect_p(&self, ray: &Ray, t_max: Option) -> bool { - self.aggregate.intersect_p(ray, t_max) - } - - pub fn unoccluded(&self, p0: &Interaction, p1: &Interaction) -> bool { - !self.intersect_p(&p0.spawn_ray_to_interaction(p1), Some(1. - SHADOW_EPSILON)) - } -} +pub use path::PathIntegrator; pub trait IntegratorTrait { fn render(&self); @@ -89,1102 +28,3 @@ pub trait RayIntegratorTrait { arena: &mut Arena, ) -> (SampledSpectrum, Option); } - -struct NextRaySample { - wi: Vector3f, - f: SampledSpectrum, - pdf: Float, - is_specular: bool, -} - -#[derive(Clone, Debug)] -pub struct SimplePathIntegrator { - base: IntegratorBase, - camera: Arc, - light_sampler: UniformLightSampler, - sample_lights: bool, - sample_bsdf: bool, - max_depth: usize, -} - -unsafe impl Send for SimplePathIntegrator {} -unsafe impl Sync for SimplePathIntegrator {} - -impl SimplePathIntegrator { - fn sample_direct_lighting( - &self, - isect: &SurfaceInteraction, - bsdf: &BSDF, - sampler: &mut Sampler, - lambda: &SampledWavelengths, - ) -> SampledSpectrum { - let Some(sampled_light) = self.light_sampler.sample(sampler.get1d()) else { - return SampledSpectrum::new(0.0); - }; - let u_light = sampler.get2d(); - let p0 = LightSampleContext::from(isect); - - let Some(ls) = sampled_light.light.sample_li(&p0, u_light, lambda, false) else { - return SampledSpectrum::new(0.0); - }; - - if ls.pdf == 0.0 || ls.l.is_black() { - return SampledSpectrum::new(0.0); - } - - let wi = ls.wi; - let wo = -isect.wo(); - - let Some(mut f) = bsdf.f(wo, wi, TransportMode::Radiance) else { - return SampledSpectrum::new(0.); - }; - - f *= wi.abs_dot(isect.shading.n.into()); - - if f.is_black() { - return SampledSpectrum::new(0.0); - } - - // Visibility check - let p0_surf = Interaction::Surface(isect.clone()); - if !self.base.unoccluded(&p0_surf, ls.p_light.as_ref()) { - return SampledSpectrum::new(0.0); - } - - f * ls.l / (sampled_light.p * ls.pdf) - } - - fn sample_bsdf_direction( - &self, - wo: Vector3f, - bsdf: &BSDF, - sampler: &mut Sampler, - _isect: &SurfaceInteraction, - ) -> Option { - let u = sampler.get1d(); - let bs = bsdf.sample_f(wo, u, sampler.get2d(), FArgs::default())?; - - Some(NextRaySample { - wi: bs.wi, - f: bs.f, - pdf: bs.pdf, - is_specular: bs.is_specular(), - }) - } - - fn sample_uniform_direction( - &self, - wo: Vector3f, - bsdf: &BSDF, - sampler: &mut Sampler, - isect: &SurfaceInteraction, - ) -> Option { - let flags = bsdf.flags(); - let mut wi; - let pdf; - - if BxDFFlags::is_reflective(&flags) && BxDFFlags::is_transmissive(&flags) { - wi = sample_uniform_sphere(sampler.get2d()); - pdf = uniform_sphere_pdf(); - } else { - wi = sample_uniform_hemisphere(sampler.get2d()); - pdf = uniform_hemisphere_pdf(); - - let same_hemi = wo.dot(isect.n().into()) * wi.dot(isect.n().into()) > 0.0; - if BxDFFlags::is_reflective(&flags) && !same_hemi - || BxDFFlags::is_transmissive(&flags) && same_hemi - { - wi = -wi; - } - } - - let f = bsdf.f(wo, wi, TransportMode::Radiance)?; - - Some(NextRaySample { - wi, - f, - pdf, - is_specular: false, - }) - } -} - -impl RayIntegratorTrait for SimplePathIntegrator { - fn evaluate_pixel_sample( - &self, - p_pixel: Point2i, - sample_ind: usize, - sampler: &mut Sampler, - arena: &mut Arena, - ) { - pipeline::evaluate_pixel_sample( - self, - self.camera.as_ref(), - sampler, - p_pixel, - sample_ind, - arena, - ); - } - - fn li( - &self, - mut ray: Ray, - lambda: &SampledWavelengths, - sampler: &mut Sampler, - _visible_surface: bool, - arena: &mut Arena, - ) -> (SampledSpectrum, Option) { - let mut l = SampledSpectrum::new(0.0); - let mut beta = SampledSpectrum::new(1.0); - let mut specular_bounce = true; - let mut depth = 0; - while !beta.is_black() { - let Some(mut si) = self.base.intersect(&ray, None) else { - if !self.sample_lights || specular_bounce { - for light in &self.base.infinite_lights { - l += light.le(&ray, lambda); - } - } - break; - }; - - let t_hit = si.t_hit(); - let isect = &mut si.intr; - if !self.sample_lights || specular_bounce { - l += beta * isect.le(-ray.d, lambda); - } - if depth == self.max_depth { - break; - } - depth += 1; - - let Some(bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), sampler) else { - specular_bounce = false; - isect.skip_intersection(&mut ray, t_hit); - continue; - }; - - let wo = -ray.d; - if self.sample_lights { - l += beta * self.sample_direct_lighting(isect, &bsdf, sampler, lambda); - } - - let sample = if self.sample_bsdf { - self.sample_bsdf_direction(wo, &bsdf, sampler, isect) - } else { - self.sample_uniform_direction(wo, &bsdf, sampler, isect) - }; - - let Some(bs) = sample else { - break; - }; - - beta *= bs.f * bs.wi.abs_dot(isect.shading.n.into()) / bs.pdf; - specular_bounce = bs.is_specular; - ray = isect.spawn_ray(bs.wi); - } - let stdspec = get_spectra_context(); - assert!(beta.y(lambda, stdspec) > 0.); - debug_assert!(beta.y(lambda, stdspec).is_finite()); - (l, None) - } -} - -#[derive(Clone, Debug)] -pub struct PathIntegrator { - base: IntegratorBase, - camera: Arc, - sampler_prototype: Sampler, - light_sampler: LightSampler, - regularize: bool, - max_depth: usize, -} - -unsafe impl Send for PathIntegrator {} -unsafe impl Sync for PathIntegrator {} - -impl PathIntegrator { - fn sample_ld( - &self, - intr: &SurfaceInteraction, - bsdf: &BSDF, - lambda: &SampledWavelengths, - sampler: &mut Sampler, - ) -> Option { - let mut ctx: LightSampleContext = intr.into(); - let flags = bsdf.flags(); - if BxDFFlags::is_reflective(&flags) ^ !BxDFFlags::is_transmissive(&flags) { - ctx.pi = Point3fi::new_from_point(intr.offset_ray_vector(-intr.wo())); - } - - let u = sampler.get1d(); - let sampled_light = self.light_sampler.sample_with_context(&ctx, u)?; - - let u_light = sampler.get2d(); - let light = sampled_light.light; - let ls = light.sample_li(&ctx, u_light, lambda, true)?; - - if ls.l.is_black() || ls.pdf == 0.0 { - return None; - } - - let wo = intr.wo(); - let wi = ls.wi; - let f = bsdf.f(wo, wi, TransportMode::Radiance)? * wi.abs_dot(intr.shading.n.into()); - - if !self - .base - .unoccluded(&Interaction::Surface(intr.clone()), &ls.p_light) - { - return None; - } - - let pl = sampled_light.p * ls.pdf; - if light.light_type().is_delta_light() { - Some(ls.l * f / pl) - } else { - let pb = bsdf.pdf(wo, wi, FArgs::default()); - let wl = power_heuristic(1, pl, 1, pb); - Some(wl * ls.l * f / pl) - } - } -} - -impl RayIntegratorTrait for PathIntegrator { - fn evaluate_pixel_sample( - &self, - p_pixel: Point2i, - sample_ind: usize, - sampler: &mut Sampler, - arena: &mut Arena, - ) { - pipeline::evaluate_pixel_sample( - self, - self.camera.as_ref(), - sampler, - p_pixel, - sample_ind, - arena, - ); - } - - fn li( - &self, - mut ray: Ray, - lambda: &SampledWavelengths, - sampler: &mut Sampler, - visible_surface: bool, - _arena: &mut Arena, - ) -> (SampledSpectrum, Option) { - let mut l = SampledSpectrum::new(0.0); - let mut beta = SampledSpectrum::new(1.0); - let mut visible: Option = None; - let mut depth = 0; - let mut pb = 1.; - let mut eta_scale = 1.; - let mut specular_bounce = false; - let mut any_non_specular_bounces = false; - - let mut prev_int_ctx = LightSampleContext::default(); - loop { - let Some(mut si) = self.base.intersect(&ray, None) else { - for light in &self.base.infinite_lights { - let le = light.le(&ray, lambda); - if depth == 0 || specular_bounce { - l += beta * le; - } else { - let pl = self.light_sampler.pmf_with_context(&prev_int_ctx, light) - * light.pdf_li(&prev_int_ctx, ray.d, true); - let w_b = power_heuristic(1, pb, 1, pl); - l += beta * w_b * le; - } - } - break; - }; - - let t_hit = si.t_hit(); - let isect = &mut si.intr; - let le = isect.le(-ray.d, lambda); - if depth == 0 || specular_bounce { - l += beta * le; - } else if let Some(area_light) = &isect.area_light { - let pl = self - .light_sampler - .pmf_with_context(&prev_int_ctx, area_light) - + area_light.pdf_li(&prev_int_ctx, ray.d, true); - let wl = power_heuristic(1, pb, 1, pl); - l += beta * wl * le; - } - - let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, self.camera.as_ref(), sampler) else { - specular_bounce = true; - isect.skip_intersection(&mut ray, t_hit); - continue; - }; - - if depth == 0 && visible_surface { - const N_RHO_SAMPLES: usize = 16; - const UC_RHO: [Float; N_RHO_SAMPLES] = [ - 0.75741637, - 0.37870818, - 0.7083487, - 0.18935409, - 0.9149363, - 0.35417435, - 0.5990858, - 0.09467703, - 0.8578725, - 0.45746812, - 0.686759, - 0.17708716, - 0.9674518, - 0.2995429, - 0.5083201, - 0.047338516, - ]; - let u_rho: [Point2f; N_RHO_SAMPLES] = [ - Point2f::new(0.855985, 0.570367), - Point2f::new(0.381823, 0.851844), - Point2f::new(0.285328, 0.764262), - Point2f::new(0.733380, 0.114073), - Point2f::new(0.542663, 0.344465), - Point2f::new(0.127274, 0.414848), - Point2f::new(0.964700, 0.947162), - Point2f::new(0.594089, 0.643463), - Point2f::new(0.095109, 0.170369), - Point2f::new(0.825444, 0.263359), - Point2f::new(0.429467, 0.454469), - Point2f::new(0.244460, 0.816459), - Point2f::new(0.756135, 0.731258), - Point2f::new(0.516165, 0.152852), - Point2f::new(0.180888, 0.214174), - Point2f::new(0.898579, 0.503897), - ]; - - let albedo = bsdf.rho_wo(isect.wo(), &UC_RHO, &u_rho); - visible = Some(VisibleSurface::new(isect, &albedo, lambda)); - } - - if self.regularize && any_non_specular_bounces { - bsdf.regularize(); - } - - if depth == self.max_depth { - break; - } - - depth += 1; - - if bsdf.flags().is_non_specular() - && let Some(ld) = self.sample_ld(isect, &bsdf, lambda, sampler) - { - l += beta * ld; - } - - let wo = -ray.d; - let u = sampler.get1d(); - let Some(bs) = bsdf.sample_f(wo, u, sampler.get2d(), FArgs::default()) else { - break; - }; - - beta *= bs.f * bs.wi.abs_dot(isect.shading.n.into()) / bs.pdf; - pb = if bs.pdf_is_proportional { - bsdf.pdf(wo, bs.wi, FArgs::default()) - } else { - bs.pdf - }; - specular_bounce = bs.is_specular(); - any_non_specular_bounces |= !bs.is_specular(); - if bs.is_transmissive() { - eta_scale *= square(bs.eta); - } - prev_int_ctx = LightSampleContext::from(&si.intr); - ray = si - .intr - .spawn_ray_with_differentials(&ray, bs.wi, bs.flags, bs.eta); - - let rr_beta = beta * eta_scale; - if rr_beta.max_component_value() < 1. && depth > 1 { - let q = (1. - rr_beta.max_component_value()).max(0.); - if sampler.get1d() < q { - break; - } - beta /= 1. - q; - } - } - - (l, visible) - } -} - -#[derive(Clone, Debug)] -pub struct SimpleVolPathIntegrator { - base: IntegratorBase, - max_depth: usize, - camera: Arc, - sampler_prototype: Sampler, -} - -impl RayIntegratorTrait for SimpleVolPathIntegrator { - fn evaluate_pixel_sample( - &self, - p_pixel: Point2i, - sample_ind: usize, - sampler: &mut Sampler, - arena: &mut Arena, - ) { - pipeline::evaluate_pixel_sample( - self, - self.camera.as_ref(), - sampler, - p_pixel, - sample_ind, - arena, - ); - } - - fn li( - &self, - mut ray: Ray, - lambda: &SampledWavelengths, - sampler: &mut Sampler, - _visible_surface: bool, - _arena: &mut Arena, - ) -> (SampledSpectrum, Option) { - let mut l = SampledSpectrum::new(0.); - let mut beta = 1.; - let mut depth = 0; - - let lambda = lambda.terminate_secondary(); - - loop { - let si = self.base.intersect(&ray, None); - let mut scattered = false; - let mut terminated = false; - let current_medium = ray.medium.take(); - if let Some(medium) = ¤t_medium { - let hash0 = hash_buffer(&sampler.get1d(), 0); - let hash1 = hash_buffer(&sampler.get1d(), 0); - let mut rng = Rng::new_with_offset(hash0, hash1); - let t_max = if let Some(hit) = &si { - hit.t_hit - } else { - Float::INFINITY - }; - let u = sampler.get1d(); - let mut u_mode = sampler.get1d(); - medium.as_ref().sample_t_maj( - ray.clone(), - t_max, - u, - &mut rng, - &lambda, - |p, mp, sigma_maj, _t_maj, rng| -> bool { - let sig_maj = sigma_maj[0]; - // Compute medium event probabilities for interaction - let p_absorb: Float = mp.sigma_a[0] / sig_maj; - let p_scatter: Float = mp.sigma_s[0] / sig_maj; - let p_null = (1. - p_absorb - p_scatter).max(0_f32); - let mode = - sample_discrete(&[p_absorb, p_scatter, p_null], u_mode, None, None); - if mode == 0 { - // Handle absorption event for medium sample - l += beta * mp.le; - terminated = true; - false - } else if mode == 1 { - // Handle regular scattering event for medium sample - if depth >= self.max_depth { - return false; - } - depth += 1; - - let u_phase = Point2f::new(rng.uniform(), rng.uniform()); - - let Some(ps) = mp.phase.sample_p(-ray.d, u_phase) else { - terminated = true; - return false; - }; - - beta *= ps.p / ps.pdf; - ray.o = p; - ray.d = ps.wi; - scattered = true; - false - } else { - // Handle null-scattering event for medium sample - u_mode = rng.uniform(); - true - } - }, - ); - } - // Handle terminated and unscattered rays after medium sampling - if terminated { - return (l, None); - } - - if scattered { - return (l, None); - } - - let Some(mut hit) = si else { - for light in &self.base.infinite_lights { - l += beta * light.le(&ray, &lambda); - } - return (l, None); - }; - - // Handle surface intersection along ray path - if let Some(bsdf) = &hit.intr.get_bsdf(&ray, &lambda, &self.camera, sampler) { - let uc = sampler.get1d(); - let u = sampler.get2d(); - if bsdf.sample_f(-ray.d, uc, u, FArgs::default()).is_some() { - panic!("SimpleVolPathIntegrator does not support surface scattering"); - } - } else { - hit.intr.skip_intersection(&mut ray, hit.t_hit); - } - } - } -} - -#[derive(Clone, Debug)] -pub struct VolPathIntegrator { - base: IntegratorBase, - sampler_prototype: Sampler, - camera: Arc, - light_sampler: LightSampler, - regularize: bool, - max_depth: usize, -} - -impl VolPathIntegrator { - fn sample_ld( - &self, - intr: &Interaction, - bsdf: Option<&BSDF>, - lambda: &SampledWavelengths, - sampler: &mut Sampler, - beta: SampledSpectrum, - r_p: SampledSpectrum, - ) -> SampledSpectrum { - let mut ctx = LightSampleContext::from(intr); - if let Some(bsdf) = bsdf { - let flags = bsdf.flags(); - let is_refl = flags.is_reflective(); - let is_trans = flags.is_transmissive(); - if is_refl ^ is_trans { - let vector = if is_refl { intr.wo() } else { -intr.wo() }; - ctx.pi = Point3fi::new_from_point(intr.offset_ray_vector(vector)); - } - } - let u = sampler.get1d(); - let Some(sampled_light) = self.light_sampler.sample_with_context(&ctx, u) else { - return SampledSpectrum::new(0.); - }; - let u_light = sampler.get2d(); - let light = sampled_light.light; - debug_assert!(sampled_light.p != 0.); - let Some(ls) = light - .sample_li(&ctx, u_light, lambda, true) - .filter(|ls| !ls.l.is_black() && ls.pdf > 0.0) - else { - return SampledSpectrum::new(0.0); - }; - let p_l = sampled_light.p * ls.pdf; - let wo = intr.wo(); - let wi = ls.wi; - let (f_hat, scatter_pdf) = match (bsdf, intr) { - (Some(bsdf), Interaction::Surface(si)) => { - let f = bsdf - .f(wo, wi, TransportMode::Radiance) - .map(|f_val| f_val * wi.abs_dot(si.shading.n.into())) - .unwrap_or(SampledSpectrum::new(0.0)); - - let pdf = bsdf.pdf(wo, wi, FArgs::default()); - (f, pdf) - } - - (None, Interaction::Medium(mi)) => { - let phase = &mi.phase; - let p = phase.p(wo, wi); - let pdf = phase.pdf(wo, wi); - - (SampledSpectrum::new(p), pdf) - } - - (Some(_), _) => panic!("BSDF provided for a non-Surface interaction"), - (None, _) => panic!("No BSDF provided for a non-Medium interaction"), - }; - - if f_hat.is_black() { - return SampledSpectrum::new(0.); - } - - let mut light_ray = intr.spawn_ray_to_interaction(ls.p_light.as_ref()); - let mut t_ray = SampledSpectrum::new(1.); - let mut r_l = SampledSpectrum::new(1.); - let mut r_u = SampledSpectrum::new(1.); - let hash0 = hash_buffer(&[light_ray.o.x(), light_ray.o.y(), light_ray.o.z()], 0); - let hash1 = hash_buffer(&[light_ray.d.x(), light_ray.d.y(), light_ray.d.z()], 0); - let mut rng = Rng::new_with_offset(hash0, hash1); - - while light_ray.d != Vector3f::zero() { - // Trace ray through media to estimate transmittance - let si = &self.base.intersect(&light_ray, Some(1. - SHADOW_EPSILON)); - if si.as_ref().filter(|s| s.intr.material.is_some()).is_some() { - return SampledSpectrum::new(0.); - } - - let current_medium = light_ray.medium.take(); - if let Some(medium) = ¤t_medium { - let t_max = if let Some(s) = si { - s.t_hit() - } else { - 1. - SHADOW_EPSILON - }; - - let u: Float = rng.uniform(); - let t_maj = medium.as_ref().sample_t_maj( - light_ray.clone(), - t_max, - u, - &mut rng, - lambda, - |_p, mp, sigma_maj, t_maj, rng| { - let sigma_n = - SampledSpectrum::clamp_zero(&(sigma_maj - mp.sigma_a - mp.sigma_s)); - let pdf = t_maj[0] * sigma_maj[0]; - t_ray *= t_maj * sigma_n / pdf; - r_l *= t_maj * sigma_maj / pdf; - r_u *= t_maj * sigma_n / pdf; - - let tr = t_ray / (r_l + r_u).average(); - if tr.max_component_value() < 0.05 { - let q = 0.75; - if rng.uniform::() < 1. { - t_ray = SampledSpectrum::new(0.); - } else { - t_ray /= 1. - q; - } - } - - if t_ray.is_black() { - return false; - } - true - }, - ); - t_ray *= t_maj / t_maj[0]; - r_l *= t_maj / t_maj[0]; - r_u *= t_maj / t_maj[0]; - } - - if t_ray.is_black() { - return SampledSpectrum::new(0.); - } - - if let Some(s) = si { - light_ray = s.intr.spawn_ray_to_interaction(ls.p_light.as_ref()); - } else { - break; - } - } - - r_l *= r_p * p_l; - r_u *= r_p * scatter_pdf; - - if light.light_type().is_delta_light() { - beta * f_hat * t_ray * ls.l / r_l.average() - } else { - beta * f_hat * t_ray * ls.l / (r_l + r_u).average() - } - } -} - -impl RayIntegratorTrait for VolPathIntegrator { - fn evaluate_pixel_sample( - &self, - p_pixel: Point2i, - sample_ind: usize, - sampler: &mut Sampler, - arena: &mut Arena, - ) { - pipeline::evaluate_pixel_sample( - self, - self.camera.as_ref(), - sampler, - p_pixel, - sample_ind, - arena, - ); - } - - fn li( - &self, - mut ray: Ray, - lambda: &SampledWavelengths, - sampler: &mut Sampler, - visible_surface: bool, - arena: &mut Arena, - ) -> (SampledSpectrum, Option) { - let mut l = SampledSpectrum::new(0.); - let mut beta = SampledSpectrum::new(1.); - let mut r_u = SampledSpectrum::new(1.); - let mut r_l = SampledSpectrum::new(1.); - let mut specular_bounce = false; - let mut any_non_specular_bounces = false; - let mut depth = 0; - let mut eta_scale = 1.; - let mut prev_int_ctx = LightSampleContext::default(); - let mut visible: Option = None; - - loop { - let si = self.base.intersect(&ray, None); - let current_medium = ray.medium.take(); - if let Some(medium) = ¤t_medium { - let mut scattered = false; - let mut terminated = false; - let hash0 = hash_buffer(&sampler.get1d(), 0); - let hash1 = hash_buffer(&sampler.get1d(), 0); - let mut rng = Rng::new_with_offset(hash0, hash1); - - let t_max = if let Some(hit) = &si { - hit.t_hit - } else { - Float::INFINITY - }; - let t_maj = medium.as_ref().sample_t_maj( - ray.clone(), - t_max, - sampler.get1d(), - &mut rng, - lambda, - |p, mp, sigma_maj, t_maj, rng| -> bool { - // Handle medium scattering event for ray - if beta.is_black() { - terminated = true; - return false; - } - - if depth < self.max_depth && !mp.le.is_black() { - let pdf = sigma_maj[0] * t_maj[0]; - let betap = beta * t_maj / pdf; - let r_e = r_u * sigma_maj * t_maj / pdf; - if !r_e.is_black() { - l += betap * mp.sigma_a * mp.le / r_e.average(); - } - } - let p_absorb = mp.sigma_a[0] / sigma_maj[0]; - let p_scatter = mp.sigma_s[0] / sigma_maj[0]; - let p_null = (1. - p_absorb - p_scatter).max(0.); - assert!(1. - p_absorb - p_scatter > -1e-6); - // Sample medium scattering event type and update path - let um = rng.uniform(); - let mode = sample_discrete(&[p_absorb, p_scatter, p_null], um, None, None); - if mode == 0 { - terminated = true; - false - } else if mode == 1 { - // Handle scattering along ray path - // Stop path sampling if maximum depth has been reached - if depth >= self.max_depth { - terminated = true; - return false; - } - depth += 1; - - let pdf = t_maj[0] * mp.sigma_s[0]; - beta *= t_maj * mp.sigma_s / pdf; - r_u *= t_maj * mp.sigma_s / pdf; - - if !beta.is_black() && !r_u.is_black() { - let ray_medium = ray.medium.expect("Void scattering").clone(); - let intr = MediumInteraction::new( - p, -ray.d, ray.time, ray_medium, mp.phase, - ); - l += self.sample_ld( - &Interaction::Medium(intr.clone()), - None, - lambda, - sampler, - beta, - r_u, - ); - let u = sampler.get2d(); - let Some(ps) = - intr.phase.sample_p(-ray.d, u).filter(|ps| ps.pdf > 0.0) - else { - terminated = true; - // Stop traversal (Path died) - return false; - }; - - beta *= ps.p / ps.pdf; - r_l = r_u / ps.pdf; - prev_int_ctx = LightSampleContext::from(&intr); - scattered = true; - ray.o = p; - ray.d = ps.wi; - specular_bounce = false; - any_non_specular_bounces = false; - } - false - } else { - let sigma_n = - SampledSpectrum::clamp_zero(&(sigma_maj - mp.sigma_a - mp.sigma_s)); - let pdf = t_maj[0] * sigma_n[0]; - beta *= t_maj * sigma_n / pdf; - if pdf == 0. { - beta = SampledSpectrum::new(0.); - } - r_u *= t_maj * sigma_n / pdf; - r_l *= t_maj * sigma_maj / pdf; - !beta.is_black() && !r_u.is_black() - } - }, - ); - - if terminated || beta.is_black() || r_u.is_black() { - return (l, None); - } - if scattered { - continue; - } - beta *= t_maj / t_maj[0]; - r_u *= t_maj / t_maj[0]; - r_l *= t_maj / t_maj[0]; - } - - // Handle surviving unscattered rays - // Add emitted light at volume path vertex or from the environment - - let stdspec = get_spectra_context(); - let Some(mut hit) = si else { - for light in &self.base.infinite_lights { - let le = light.le(&ray, lambda); - if !le.is_black() { - if depth == 0 || specular_bounce { - l += beta * light.le(&ray, lambda); - } - } else { - // Add infinite light contribution using both PDFs with MIS - let p_l = self.light_sampler.pmf_with_context(&prev_int_ctx, light) - * light.pdf_li(&prev_int_ctx, ray.d, true); - r_l *= p_l; - l += beta * le / (r_u + r_l).average(); - } - } - break; - }; - - let le = &hit.intr.le(-ray.d, lambda); - if !le.is_black() { - if depth == 0 || specular_bounce { - l += beta * *le / r_u.average(); - } - } else { - // Add surface light contribution using both PDFs with MIS - let area_light = hit.intr.area_light.as_ref().expect("No surface light"); - let p_l = self - .light_sampler - .pmf_with_context(&prev_int_ctx, area_light); - r_l *= p_l; - l += beta * *le / (r_u + r_l).average(); - } - - let isect = &mut hit.intr; - let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler) else { - hit.intr.skip_intersection(&mut ray, hit.t_hit()); - continue; - }; - - if depth == 0 && visible_surface { - const N_RHO_SAMPLES: usize = 16; - const UC_RHO: [Float; N_RHO_SAMPLES] = [ - 0.75741637, - 0.37870818, - 0.7083487, - 0.18935409, - 0.9149363, - 0.35417435, - 0.5990858, - 0.09467703, - 0.8578725, - 0.45746812, - 0.686759, - 0.17708716, - 0.9674518, - 0.2995429, - 0.5083201, - 0.047338516, - ]; - let u_rho: [Point2f; N_RHO_SAMPLES] = [ - Point2f::new(0.855985, 0.570367), - Point2f::new(0.381823, 0.851844), - Point2f::new(0.285328, 0.764262), - Point2f::new(0.733380, 0.114073), - Point2f::new(0.542663, 0.344465), - Point2f::new(0.127274, 0.414848), - Point2f::new(0.964700, 0.947162), - Point2f::new(0.594089, 0.643463), - Point2f::new(0.095109, 0.170369), - Point2f::new(0.825444, 0.263359), - Point2f::new(0.429467, 0.454469), - Point2f::new(0.244460, 0.816459), - Point2f::new(0.756135, 0.731258), - Point2f::new(0.516165, 0.152852), - Point2f::new(0.180888, 0.214174), - Point2f::new(0.898579, 0.503897), - ]; - let albedo = bsdf.rho_wo(isect.wo(), &UC_RHO, &u_rho); - visible = Some(VisibleSurface::new(isect, &albedo, lambda)); - } - - if depth >= self.max_depth { - return (l, visible); - } - - depth += 1; - - if self.regularize && any_non_specular_bounces { - bsdf.regularize(); - } - - if BxDFFlags::is_non_specular(&bsdf.flags()) { - l += self.sample_ld( - &Interaction::Surface(isect.clone()), - Some(&bsdf), - lambda, - sampler, - beta, - r_u, - ); - debug_assert!(l.y(lambda, stdspec).is_finite()); - } - let wo = isect.wo(); - let n = isect.shading.n; - prev_int_ctx = LightSampleContext::from(&*isect); - let u = sampler.get1d(); - let Some(bs) = bsdf.sample_f(wo, u, sampler.get2d(), FArgs::default()) else { - break; - }; - beta *= bs.f * bs.wi.abs_dot(n.into()) / bs.pdf; - if bs.pdf_is_proportional { - r_l = r_u / bsdf.pdf(wo, bs.wi, FArgs::default()); - } else { - r_l = r_u / bs.pdf; - } - debug_assert!(beta.y(lambda, stdspec).is_finite()); - // Update volumetric integrator path state after surface scattering - specular_bounce = bs.is_specular(); - if bs.is_transmissive() { - eta_scale *= square(bs.eta); - } - ray = isect.spawn_ray_with_differentials(&ray, bs.wi, bs.flags, bs.eta); - - if let Some(bssrdf) = (*isect).get_bssrdf(&ray, lambda, self.camera.as_ref()) - && bs.is_transmissive() - { - let uc = sampler.get1d(); - let up = sampler.get2d(); - let Some(probe_seg) = bssrdf.sample_sp(uc, up) else { - break; - }; - let seed = mix_bits(float_to_bits(sampler.get1d()).into()); - let mut interaction_sampler = - WeightedReservoirSampler::::new(seed); - let base = SimpleInteraction::new(Point3fi::new_from_point(probe_seg.p0), ray.time); - loop { - let r = base.spawn_ray_to_point(probe_seg.p1); - if r.d == Vector3f::zero() { - break; - } - let Some(si) = self.base.intersect(&r, Some(1.)) else { - break; - }; - - let materials_match = match (&si.intr.material, &isect.material) { - (Some(m1), Some(m2)) => Arc::ptr_eq(m1, m2), - _ => false, - }; - - if materials_match { - interaction_sampler.add(SubsurfaceInteraction::new(&si.intr), 1.); - } - } - - if !interaction_sampler.has_sample() { - break; - } - - if let Some(ssi) = interaction_sampler.get_sample() { - let bssrdf_sample = bssrdf.probe_intersection_to_sample(ssi); - if bssrdf_sample.sp.is_black() || bssrdf_sample.pdf.is_black() { - break; - } - let pdf = interaction_sampler.sample_probability() * bssrdf_sample.pdf[0]; - beta *= bssrdf_sample.sp / pdf; - r_u *= bssrdf_sample.pdf / bssrdf_sample.pdf[0]; - let mut pi = SurfaceInteraction::from(ssi); - pi.common.wo = bssrdf_sample.wo; - prev_int_ctx = LightSampleContext::from(&pi); - let mut sw = bssrdf_sample.sw; - if self.regularize { - sw.regularize(); - } - - l += self.sample_ld( - &Interaction::Surface(pi.clone()), - Some(&sw), - lambda, - sampler, - beta, - r_u, - ); - - let u = sampler.get1d(); - let Some(bs) = sw.sample_f(pi.wo(), u, sampler.get2d(), FArgs::default()) - else { - break; - }; - beta *= bs.f * bs.wi.abs_dot(pi.shading.n.into()) / bs.pdf; - r_l = r_u / bs.pdf; - debug_assert!(beta.y(lambda).is_finite()); - specular_bounce = bs.is_specular(); - ray = pi.spawn_ray(bs.wi); - } - } - - if beta.is_black() { - break; - } - - let rr_beta = beta * eta_scale / r_u.average(); - let u_rr = sampler.get1d(); - if rr_beta.max_component_value() < 1. && depth > 1 { - let q = (1. - rr_beta.max_component_value()).max(0.); - if u_rr < 1. { - break; - } - beta /= 1. - q; - } - } - - (l, visible) - } -} - -pub enum Integrator { - BPDT(BPDTIntegrator), - MLT(MLTIntegrator), - SPPM(SPPMIntegrator), - Sampler(SamplerIntegrator), -} - -pub struct BPDTIntegrator; -pub struct MLTIntegrator; -pub struct SPPMIntegrator; -pub struct SamplerIntegrator; diff --git a/src/integrators/path.rs b/src/integrators/path.rs new file mode 100644 index 0000000..c937e31 --- /dev/null +++ b/src/integrators/path.rs @@ -0,0 +1,320 @@ +use super::RayIntegratorTrait; +use super::base::IntegratorBase; +use super::constants::*; +use super::state::PathState; +use crate::Arena; +use shared::core::bsdf::{BSDF, BSDFSample}; +use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode}; +use shared::core::camera::Camera; +use shared::core::film::VisibleSurface; +use shared::core::geometry::{Point2i, Ray, Vector3f, VectorLike}; +use shared::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; +use shared::core::light::{Light, LightSampleContext}; +use shared::core::primitive::Primitive; +use shared::core::sampler::{Sampler, SamplerTrait}; +use shared::lights::sampler::LightSampler; +use shared::lights::sampler::LightSamplerTrait; +use shared::spectra::{SampledSpectrum, SampledWavelengths}; +use shared::utils::math::square; +use shared::utils::sampling::{ + power_heuristic, sample_uniform_hemisphere, sample_uniform_sphere, uniform_hemisphere_pdf, + uniform_sphere_pdf, +}; +use std::sync::Arc; + +#[derive(Clone, Copy)] +pub struct PathConfig { + pub max_depth: usize, + pub regularize: bool, + pub sample_lights: bool, + pub sample_bsdf: bool, + pub handle_volumes: bool, + pub use_mis: bool, +} + +impl PathConfig { + pub const SIMPLE: Self = Self { + max_depth: 5, + regularize: false, + sample_lights: true, + sample_bsdf: true, + handle_volumes: false, + use_mis: false, + }; + + pub const FULL: Self = Self { + max_depth: 8, + regularize: true, + sample_lights: true, + sample_bsdf: true, + handle_volumes: false, + use_mis: true, + }; + + pub const VOLUMETRIC: Self = Self { + max_depth: 8, + regularize: true, + sample_lights: true, + sample_bsdf: true, + handle_volumes: true, + use_mis: true, + }; +} + +pub struct PathIntegrator { + base: IntegratorBase, + camera: Arc, + light_sampler: LightSampler, + config: PathConfig, +} + +unsafe impl Send for PathIntegrator {} +unsafe impl Sync for PathIntegrator {} + +impl PathIntegrator { + pub fn new( + aggregate: Arc, + lights: Vec>, + camera: Arc, + config: PathConfig, + ) -> Self { + let base = IntegratorBase::new(aggregate, lights.clone()); + let light_sampler = LightSampler::new(&lights); + Self { + base, + camera, + light_sampler, + config, + } + } + + fn sample_direct( + &self, + intr: &SurfaceInteraction, + bsdf: &BSDF, + state: &PathState, + lambda: &SampledWavelengths, + sampler: &mut Sampler, + ) -> SampledSpectrum { + let ctx = LightSampleContext::from(intr); + + let Some(sampled) = self + .light_sampler + .sample_with_context(&ctx, sampler.get1d()) + else { + return SampledSpectrum::ZERO; + }; + + let Some(ls) = sampled.light.sample_li(&ctx, sampler.get2d(), lambda, true) else { + return SampledSpectrum::ZERO; + }; + + if ls.l.is_black() || ls.pdf == 0.0 { + return SampledSpectrum::ZERO; + } + + let wo = intr.wo(); + let wi = ls.wi; + + let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) else { + return SampledSpectrum::ZERO; + }; + + let f = f * wi.abs_dot(intr.shading.n.into()); + if f.is_black() { + return SampledSpectrum::ZERO; + } + + if !self + .base + .unoccluded(&Interaction::Surface(intr.clone()), &ls.p_light) + { + return SampledSpectrum::ZERO; + } + + let p_l = sampled.p * ls.pdf; + + if !self.config.use_mis || sampled.light.light_type().is_delta_light() { + ls.l * f / p_l + } else { + let p_b = bsdf.pdf(wo, wi, FArgs::default()); + let w_l = power_heuristic(1, p_l, 1, p_b); + w_l * ls.l * f / p_l + } + } + + fn sample_direction( + &self, + wo: Vector3f, + bsdf: &BSDF, + isect: &SurfaceInteraction, + sampler: &mut Sampler, + ) -> Option { + if self.config.sample_bsdf { + bsdf.sample_f(wo, sampler.get1d(), sampler.get2d(), FArgs::default()) + } else { + self.sample_uniform(wo, bsdf, isect, sampler) + } + } + + fn sample_uniform( + &self, + wo: Vector3f, + bsdf: &BSDF, + isect: &SurfaceInteraction, + sampler: &mut Sampler, + ) -> Option { + let flags = bsdf.flags(); + + let (wi, pdf) = if flags.is_reflective() && flags.is_transmissive() { + (sample_uniform_sphere(sampler.get2d()), uniform_sphere_pdf()) + } else { + let mut wi = sample_uniform_hemisphere(sampler.get2d()); + let same_hemi = wo.dot(isect.n().into()) * wi.dot(isect.n().into()) > 0.0; + + if (flags.is_reflective() && !same_hemi) || (flags.is_transmissive() && same_hemi) { + wi = -wi; + } + (wi, uniform_hemisphere_pdf()) + }; + + let f = bsdf.f(wo, wi, TransportMode::Radiance)?; + + Some(BSDFSample { + f, + wi, + pdf, + flags: BxDFFlags::empty(), // or appropriate flags + eta: 1.0, + pdf_is_proportional: false, + }) + } + + fn compute_visible_surface( + &self, + isect: &SurfaceInteraction, + bsdf: &BSDF, + lambda: &SampledWavelengths, + ) -> VisibleSurface { + let albedo = bsdf.rho_wo(isect.wo(), &UC_RHO, &U_RHO); + VisibleSurface::new(isect, &albedo, lambda) + } +} + +impl RayIntegratorTrait for PathIntegrator { + fn li( + &self, + mut ray: Ray, + lambda: &SampledWavelengths, + sampler: &mut Sampler, + want_visible: bool, + _arena: &mut Arena, + ) -> (SampledSpectrum, Option) { + let mut state = PathState::new(); + let mut visible = None; + + loop { + let Some(mut si) = self.base.intersect(&ray, None) else { + self.base.add_infinite_light_contribution( + &mut state, + &ray, + lambda, + Some(&self.light_sampler), + self.config.use_mis, + ); + break; + }; + + let isect = &mut si.intr; + + // Emission from hit surface + let le = isect.le(-ray.d, lambda); + if !le.is_black() { + if state.depth == 0 || state.specular_bounce { + state.l += state.beta * le; + } else if self.config.use_mis { + if let Some(light) = &isect.area_light { + let p_l = self.light_sampler.pmf_with_context(&state.prev_ctx, light) + * light.pdf_li(&state.prev_ctx, ray.d, true); + let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); + state.l += state.beta * w_b * le; + } + } + } + + // Get BSDF + let Some(mut bsdf) = isect.get_bsdf(&ray, lambda, &self.camera, sampler) else { + state.specular_bounce = true; + isect.skip_intersection(&mut ray, si.t_hit()); + continue; + }; + + // Visible surface at primary hit + if state.depth == 0 && want_visible { + visible = Some(self.compute_visible_surface(isect, &bsdf, lambda)); + } + + // Depth check + if state.depth >= self.config.max_depth { + break; + } + state.depth += 1; + + // Regularization + if self.config.regularize && state.any_non_specular_bounces { + bsdf.regularize(); + } + + // Direct lighting + if self.config.sample_lights && bsdf.flags().is_non_specular() { + state.l += state.beta * self.sample_direct(isect, &bsdf, &state, lambda, sampler); + } + + // Sample BSDF for next direction + let wo = -ray.d; + let Some(bs) = self.sample_direction(wo, &bsdf, isect, sampler) else { + break; + }; + + state.beta *= bs.f * bs.wi.abs_dot(isect.shading.n.into()) / bs.pdf; + state.prev_pdf = if bs.pdf_is_proportional { + bsdf.pdf(wo, bs.wi, FArgs::default()) + } else { + bs.pdf + }; + state.specular_bounce = bs.is_specular(); + state.any_non_specular_bounces |= !bs.is_specular(); + + if bs.is_transmissive() { + state.eta_scale *= square(bs.eta); + } + + state.prev_ctx = LightSampleContext::from(&*isect); + ray = isect.spawn_ray_with_differentials(&ray, bs.wi, bs.flags, bs.eta); + + // Russian roulette + if state.russian_roulette(sampler, 1) { + break; + } + } + + (state.l, visible) + } + + fn evaluate_pixel_sample( + &self, + p_pixel: Point2i, + sample_ind: usize, + sampler: &mut Sampler, + arena: &mut Arena, + ) { + crate::integrators::pipeline::evaluate_pixel_sample( + self, + self.camera.as_ref(), + sampler, + p_pixel, + sample_ind, + arena, + ); + } +} diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 8336f17..0f652c6 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -1,12 +1,19 @@ +use super::RayIntegratorTrait; +use super::base::IntegratorBase; +use crate::Arena; use crate::core::image::{Image, ImageMetadata}; use crate::spectra::get_spectra_context; use indicatif::{ProgressBar, ProgressStyle}; +use shared::Float; +use shared::core::camera::Camera; +use shared::core::geometry::{Bounds2i, Point2i}; +use shared::core::options::get_options; +use shared::core::sampler::Sampler; use shared::core::sampler::get_camera_sample; +use shared::spectra::SampledSpectrum; use std::io::Write; use std::path::Path; -use super::*; - struct PbrtProgress { bar: ProgressBar, } diff --git a/src/integrators/state.rs b/src/integrators/state.rs new file mode 100644 index 0000000..0dfdcf9 --- /dev/null +++ b/src/integrators/state.rs @@ -0,0 +1,45 @@ +use shared::Float; +use shared::core::light::LightSampleContext; +use shared::core::sampler::{Sampler, SamplerTrait}; +use shared::spectra::SampledSpectrum; + +pub struct PathState { + pub l: SampledSpectrum, + pub beta: SampledSpectrum, + pub depth: usize, + pub specular_bounce: bool, + pub any_non_specular_bounces: bool, + pub eta_scale: Float, + pub prev_ctx: LightSampleContext, +} + +impl PathState { + pub fn new() -> Self { + Self { + l: SampledSpectrum::new(0.0), + beta: SampledSpectrum::new(1.0), + depth: 0, + specular_bounce: false, + any_non_specular_bounces: false, + eta_scale: 1.0, + prev_ctx: LightSampleContext::default(), + } + } + + pub fn terminated(&self) -> bool { + self.beta.is_black() + } + + /// Termination check + pub fn russian_roulette(&mut self, sampler: &mut Sampler, min_depth: usize) -> bool { + let rr_beta = self.beta * self.eta_scale; + if rr_beta.max_component_value() < 1.0 && self.depth > min_depth { + let q = (1.0 - rr_beta.max_component_value()).max(0.0); + if sampler.get1d() < q { + return true; // terminate + } + self.beta /= 1.0 - q; + } + false + } +}