pbrt/src/core/bssrdf.rs

327 lines
9.9 KiB
Rust

use crate::core::bxdf::BSDF;
use crate::core::interaction::{InteractionData, ShadingGeometry, SurfaceInteraction};
use crate::core::pbrt::{Float, PI};
use crate::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::shapes::Shape;
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::{catmull_rom_weights, square};
use crate::utils::sampling::sample_catmull_rom_2d;
use enum_dispatch::enum_dispatch;
use std::sync::Arc;
#[derive(Debug)]
pub struct BSSRDFSample<'a> {
pub sp: SampledSpectrum,
pub pdf: SampledSpectrum,
pub sw: BSDF<'a>,
pub wo: Vector3f,
}
#[derive(Clone, Debug)]
pub struct SubsurfaceInteraction {
pi: Point3fi,
n: Normal3f,
ns: Normal3f,
dpdu: Vector3f,
dpdv: Vector3f,
dpdus: Vector3f,
dpdvs: Vector3f,
}
impl SubsurfaceInteraction {
pub fn new(si: &SurfaceInteraction) -> Self {
Self {
pi: si.common.pi,
n: si.common.n,
dpdu: si.dpdu,
dpdv: si.dpdv,
ns: si.shading.n,
dpdus: si.shading.dpdu,
dpdvs: si.shading.dpdv,
}
}
pub fn p(&self) -> Point3f {
self.pi.into()
}
}
impl From<SurfaceInteraction> for SubsurfaceInteraction {
fn from(si: SurfaceInteraction) -> SubsurfaceInteraction {
SubsurfaceInteraction {
pi: si.common.pi,
n: si.common.n,
ns: si.shading.n,
dpdu: si.dpdu,
dpdv: si.dpdv,
dpdus: si.shading.dpdu,
dpdvs: si.shading.dpdv,
}
}
}
impl From<&SubsurfaceInteraction> for SurfaceInteraction {
fn from(ssi: &SubsurfaceInteraction) -> SurfaceInteraction {
SurfaceInteraction {
common: InteractionData {
pi: ssi.pi,
n: ssi.n,
wo: Vector3f::zero(),
time: 0.,
medium_interface: None,
medium: None,
},
uv: Point2f::zero(),
dpdu: ssi.dpdu,
dpdv: ssi.dpdv,
dndu: Normal3f::zero(),
dndv: Normal3f::zero(),
shading: ShadingGeometry {
n: ssi.ns,
dpdu: ssi.dpdus,
dpdv: ssi.dpdvs,
dndu: Normal3f::zero(),
dndv: Normal3f::zero(),
},
face_index: 0,
area_light: None,
material: None,
dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(),
dudx: 0.,
dvdx: 0.,
dudy: 0.,
dvdy: 0.,
shape: Shape::default().into(),
}
}
}
#[derive(Clone, Debug)]
pub struct BSSRDFTable {
rho_samples: Vec<Float>,
radius_samples: Vec<Float>,
profile: Vec<Float>,
rho_eff: Vec<Float>,
profile_cdf: Vec<Float>,
}
impl BSSRDFTable {
pub fn new(n_rho_samples: usize, n_radius_samples: usize) -> Self {
let rho_samples: Vec<Float> = Vec::with_capacity(n_rho_samples);
let radius_samples: Vec<Float> = Vec::with_capacity(n_radius_samples);
let profile: Vec<Float> = Vec::with_capacity(n_radius_samples * n_rho_samples);
let rho_eff: Vec<Float> = Vec::with_capacity(n_rho_samples);
let profile_cdf: Vec<Float> = Vec::with_capacity(n_radius_samples * n_rho_samples);
Self {
rho_samples,
radius_samples,
profile,
rho_eff,
profile_cdf,
}
}
pub fn eval_profile(&self, rho_index: usize, radius_index: usize) -> Float {
assert!(rho_index < self.rho_samples.len());
assert!(radius_index < self.radius_samples.len());
self.profile[rho_index * self.radius_samples.len() + radius_index]
}
}
#[derive(Clone, Default, Debug)]
pub struct BSSRDFProbeSegment {
pub p0: Point3f,
pub p1: Point3f,
}
#[enum_dispatch]
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
fn probe_intersection_to_sample(&self, si: &SubsurfaceInteraction) -> BSSRDFSample<'_>;
}
#[enum_dispatch(BSSRDFTrait)]
#[derive(Debug, Clone)]
pub enum BSSRDF<'a> {
Tabulated(TabulatedBSSRDF<'a>),
}
#[derive(Clone, Debug)]
pub struct TabulatedBSSRDF<'a> {
po: Point3f,
wo: Vector3f,
ns: Normal3f,
eta: Float,
sigma_t: SampledSpectrum,
rho: SampledSpectrum,
table: &'a BSSRDFTable,
}
impl<'a> TabulatedBSSRDF<'a> {
pub fn new(
po: Point3f,
wo: Vector3f,
ns: Normal3f,
eta: Float,
sigma_a: &SampledSpectrum,
sigma_s: &SampledSpectrum,
table: &'a BSSRDFTable,
) -> Self {
let sigma_t = *sigma_a + *sigma_s;
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
Self {
po,
wo,
ns,
eta,
table,
sigma_t,
rho,
}
}
pub fn sp(&self, pi: Point3f) -> SampledSpectrum {
self.sr(self.po.distance(pi))
}
pub fn sr(&self, r: Float) -> SampledSpectrum {
let mut sr_spectrum = SampledSpectrum::new(0.);
for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) =
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
Some(res) => res,
None => continue,
};
let (radius_offset, radius_weights) =
match catmull_rom_weights(&self.table.radius_samples, r_optical) {
Some(res) => res,
None => continue,
};
let mut sr = 0.;
for (j, rho_weight) in rho_weights.iter().enumerate() {
for (k, radius_weight) in radius_weights.iter().enumerate() {
let weight = rho_weight * radius_weight;
if weight != 0. {
sr += weight * self.table.eval_profile(rho_offset + j, radius_offset + k);
}
}
}
if r_optical != 0. {
sr /= 2. * PI * r_optical;
}
sr_spectrum[i] = sr;
}
sr_spectrum *= self.sigma_t * self.sigma_t;
SampledSpectrum::clamp_zero(&sr_spectrum)
}
pub fn sample_sr(&self, u: Float) -> Option<Float> {
if self.sigma_t[0] == 0. {
return None;
}
let (ret, _, _) = sample_catmull_rom_2d(
&self.table.rho_samples,
&self.table.radius_samples,
&self.table.profile,
&self.table.profile_cdf,
self.rho[0],
u,
);
Some(ret / self.sigma_t[0])
}
pub fn pdf_sr(&self, r: Float) -> SampledSpectrum {
let mut pdf = SampledSpectrum::new(0.);
for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) =
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
Some(res) => res,
None => continue,
};
let (radius_offset, radius_weights) =
match catmull_rom_weights(&self.table.radius_samples, r_optical) {
Some(res) => res,
None => continue,
};
let mut sr = 0.;
let mut rho_eff = 0.;
for (j, rho_weight) in rho_weights.iter().enumerate() {
if *rho_weight != 0. {
// Update _rhoEff_ and _sr_ for wavelength
rho_eff += self.table.rho_eff[rho_offset + j] * rho_weight;
// Fix: Use .iter().enumerate() for 'k'
for (k, radius_weight) in radius_weights.iter().enumerate() {
if *radius_weight != 0. {
sr += self.table.eval_profile(rho_offset + j, radius_offset + k)
* rho_weight
* radius_weight;
}
}
}
}
// Cancel marginal PDF factor from tabulated BSSRDF profile
if r_optical != 0. {
sr /= 2. * PI * r_optical;
}
pdf[i] = sr * square(self.sigma_t[i]) / rho_eff;
}
SampledSpectrum::clamp_zero(&pdf)
}
pub fn pdf_sp(&self, pi: Point3f, ni: Normal3f) -> SampledSpectrum {
let d = pi - self.po;
let f = Frame::from_z(self.ns.into());
let d_local = f.to_local(d);
let n_local = f.to_local(ni.into());
let r_proj = [
(square(d_local.y() + square(d_local.z()))).sqrt(),
(square(d_local.z() + square(d_local.x()))).sqrt(),
(square(d_local.x() + square(d_local.y()))).sqrt(),
];
let axis_prob = [0.25, 0.25, 0.25];
let mut pdf = SampledSpectrum::new(0.);
for axis in 0..3 {
pdf += self.pdf_sr(r_proj[axis] * n_local[axis].abs() * axis_prob[axis]);
}
pdf
}
}
impl<'a> BSSRDFTrait for TabulatedBSSRDF<'a> {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment> {
let f = if u1 < 0.25 {
Frame::from_x(self.ns.into())
} else if u1 < 0.5 {
Frame::from_y(self.ns.into())
} else {
Frame::from_z(self.ns.into())
};
let r = self.sample_sr(u2[0])?;
let phi = 2. * PI * u2[1];
let r_max = self.sample_sr(0.999)?;
let l = 2. * (square(r_max) - square(r)).sqrt();
let p_start = self.po + r * (f.x * phi.cos() + f.y * phi.sin()) - l * f.z / 2.;
let p_target = p_start + l * f.z;
Some(BSSRDFProbeSegment {
p0: p_start,
p1: p_target,
})
}
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample<'_> {
todo!()
}
}