pbrt/src/integrators/path.rs

319 lines
9.2 KiB
Rust

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<Camera>,
sampler: LightSampler,
config: PathConfig,
}
unsafe impl Send for PathIntegrator {}
unsafe impl Sync for PathIntegrator {}
impl PathIntegrator {
pub fn new(
aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>,
camera: Arc<Camera>,
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<BSDFSample> {
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<BSDFSample> {
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<VisibleSurface>) {
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,
);
}
}