1187 lines
41 KiB
Rust
1187 lines
41 KiB
Rust
mod pipeline;
|
|
|
|
use shared::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
|
|
use shared::core::bxdf::{BSDF, BxDFFlags, BxDFTrait, 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::lights::sampler::LightSamplerTrait;
|
|
use shared::lights::sampler::{LightSampler, UniformLightSampler};
|
|
use shared::shapes::ShapeIntersection;
|
|
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;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct IntegratorBase {
|
|
pub aggregate: Arc<Primitive>,
|
|
pub lights: Vec<Arc<Light>>,
|
|
pub infinite_lights: Vec<Arc<Light>>,
|
|
}
|
|
|
|
impl IntegratorBase {
|
|
pub fn new(aggregate: Arc<Primitive>, lights: Vec<Arc<Light>>) -> 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<Float>) -> Option<ShapeIntersection> {
|
|
self.aggregate.intersect(ray, t_max)
|
|
}
|
|
|
|
pub fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> 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 trait IntegratorTrait {
|
|
fn render(&self);
|
|
}
|
|
|
|
pub trait RayIntegratorTrait {
|
|
fn evaluate_pixel_sample(
|
|
&self,
|
|
p_pixel: Point2i,
|
|
sample_ind: usize,
|
|
sampler: &mut Sampler,
|
|
arena: &mut Arena,
|
|
);
|
|
|
|
fn li(
|
|
&self,
|
|
ray: Ray,
|
|
lambda: &SampledWavelengths,
|
|
sampler: &mut Sampler,
|
|
visible_surface: bool,
|
|
arena: &mut Arena,
|
|
) -> (SampledSpectrum, Option<VisibleSurface>);
|
|
}
|
|
|
|
struct NextRaySample {
|
|
wi: Vector3f,
|
|
f: SampledSpectrum,
|
|
pdf: Float,
|
|
is_specular: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct SimplePathIntegrator {
|
|
base: IntegratorBase,
|
|
camera: Arc<Camera>,
|
|
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<NextRaySample> {
|
|
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<NextRaySample> {
|
|
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<VisibleSurface>) {
|
|
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);
|
|
}
|
|
assert!(beta.y(lambda) > 0.);
|
|
debug_assert!(beta.y(lambda).is_finite());
|
|
(l, None)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct PathIntegrator {
|
|
base: IntegratorBase,
|
|
camera: Arc<Camera>,
|
|
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<SampledSpectrum> {
|
|
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<VisibleSurface>) {
|
|
let mut l = SampledSpectrum::new(0.0);
|
|
let mut beta = SampledSpectrum::new(1.0);
|
|
let mut visible: Option<VisibleSurface> = 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<Camera>,
|
|
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<VisibleSurface>) {
|
|
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<Camera>,
|
|
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::<Float>() < 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,
|
|
scratch,
|
|
);
|
|
}
|
|
|
|
fn li(
|
|
&self,
|
|
mut ray: Ray,
|
|
lambda: &SampledWavelengths,
|
|
sampler: &mut Sampler,
|
|
visible_surface: bool,
|
|
arena: &mut Arena,
|
|
) -> (SampledSpectrum, Option<VisibleSurface>) {
|
|
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<VisibleSurface> = 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.as_ref().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 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).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).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(), scratch)
|
|
&& 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::<SubsurfaceInteraction>::new(seed);
|
|
let base =
|
|
SimpleInteraction::new(Point3fi::new_from_point(probe_seg.p0), ray.time, None);
|
|
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;
|