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::LightTrait; 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, 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, sampler: LightSampler, config: PathConfig, ) -> Self { let base = IntegratorBase::new(aggregate, lights.clone()); Self { base, camera, 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.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: &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.sampler), self.config.use_mis, ); break; }; let t_hit = si.t_hit(); 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 !isect.area_light.is_null() { let light = &isect.area_light; let p_l = self.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, 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); 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: &Arena, ) { crate::integrators::pipeline::evaluate_pixel_sample( self, self.camera.as_ref(), sampler, p_pixel, sample_ind, arena, ); } }