pbrt/shared/src/core/bssrdf.rs

346 lines
10 KiB
Rust

use crate::bxdfs::NormalizedFresnelBxDF;
use crate::core::bsdf::BSDF;
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction};
use crate::core::shape::Shape;
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::Ptr;
use crate::utils::math::{catmull_rom_weights, square};
use crate::utils::sampling::sample_catmull_rom_2d;
use crate::{Float, PI};
use enum_dispatch::enum_dispatch;
use std::sync::Arc;
#[derive(Debug)]
pub struct BSSRDFSample {
pub sp: SampledSpectrum,
pub pdf: SampledSpectrum,
pub sw: BSDF,
pub wo: Vector3f,
}
#[derive(Clone, Debug)]
pub struct SubsurfaceInteraction {
pub pi: Point3fi,
pub n: Normal3f,
pub ns: Normal3f,
pub dpdu: Vector3f,
pub dpdv: Vector3f,
pub dpdus: Vector3f,
pub 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: InteractionBase::new_minimal(ssi.pi, ssi.n),
dpdu: ssi.dpdu,
dpdv: ssi.dpdv,
dndu: Normal3f::zero(),
dndv: Normal3f::zero(),
shading: ShadingGeom {
n: ssi.ns,
dpdu: ssi.dpdus,
dpdv: ssi.dpdvs,
dndu: Normal3f::zero(),
dndv: Normal3f::zero(),
},
face_index: 0,
area_light: Ptr::null(),
material: Ptr::null(),
dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(),
dudx: 0.,
dvdx: 0.,
dudy: 0.,
dvdy: 0.,
shape: Ptr::from(&Shape::default()),
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct BSSRDFTable {
pub n_rho: u32,
pub n_radius: u32,
pub rho_samples: Ptr<Float>,
pub radius_samples: Ptr<Float>,
pub profile: Ptr<Float>,
pub rho_eff: Ptr<Float>,
pub profile_cdf: Ptr<Float>,
}
impl BSSRDFTable {
pub fn get_rho(&self) -> &[Float] {
unsafe { core::slice::from_raw_parts(self.rho_samples.as_ref(), self.n_rho as usize) }
}
pub fn get_radius(&self) -> &[Float] {
unsafe { core::slice::from_raw_parts(self.radius_samples.as_ref(), self.n_radius as usize) }
}
pub fn get_profile(&self) -> &[Float] {
let n_profile = (self.n_rho * self.n_radius) as usize;
unsafe { core::slice::from_raw_parts(self.profile.as_ref(), n_profile) }
}
pub fn get_cdf(&self) -> &[Float] {
let n_profile = (self.n_rho * self.n_radius) as usize;
unsafe { core::slice::from_raw_parts(self.profile_cdf.as_ref(), n_profile) }
}
pub fn eval_profile(&self, rho_index: u32, radius_index: u32) -> Float {
debug_assert!(rho_index < self.n_rho);
debug_assert!(radius_index < self.n_radius);
let idx = (rho_index * self.n_radius + radius_index) as usize;
unsafe { *self.profile.add(idx) }
}
}
#[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
pub struct BSSRDFProbeSegment {
pub p0: Point3f,
pub p1: Point3f,
}
#[enum_dispatch]
pub trait BSSRDFTrait {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
fn probe_intersection_to_sample(
&self,
si: &SubsurfaceInteraction,
bxdf: NormalizedFresnelBxDF,
) -> BSSRDFSample;
}
#[repr(C)]
#[enum_dispatch(BSSRDFTrait)]
#[derive(Debug, Copy, Clone)]
pub enum BSSRDF {
Tabulated(TabulatedBSSRDF),
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TabulatedBSSRDF {
po: Point3f,
wo: Vector3f,
ns: Normal3f,
eta: Float,
sigma_t: SampledSpectrum,
rho: SampledSpectrum,
table: Ptr<BSSRDFTable>,
}
impl TabulatedBSSRDF {
pub fn new(
po: Point3f,
wo: Vector3f,
ns: Normal3f,
eta: Float,
sigma_a: &SampledSpectrum,
sigma_s: &SampledSpectrum,
table: &BSSRDFTable,
) -> Self {
let sigma_t = *sigma_a + *sigma_s;
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
Self {
po,
wo,
ns,
eta,
sigma_t,
rho,
table: Ptr::from(table),
}
}
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.);
let rho_samples = self.table.get_rho();
let radius_samples = self.table.get_radius();
for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) = match catmull_rom_weights(rho_samples, self.rho[i]) {
Some(res) => res,
None => continue,
};
let (radius_offset, radius_weights) =
match catmull_rom_weights(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 as u32, radius_offset + k as u32);
}
}
}
if r_optical != 0. {
sr /= 2. * PI * r_optical;
}
sr_spectrum[i] = sr;
}
sr_spectrum *= square(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 rho_samples = self.table.get_rho();
let radius_samples = self.table.get_radius();
let profile = self.table.get_profile();
let cdf = self.table.get_cdf();
let (ret, _, _) =
sample_catmull_rom_2d(rho_samples, radius_samples, 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.);
let rhoeff_samples = self.table.get_rho();
let radius_samples = self.table.get_radius();
for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) = match catmull_rom_weights(rhoeff_samples, self.rho[i]) {
Some(res) => res,
None => continue,
};
let (radius_offset, radius_weights) =
match catmull_rom_weights(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 += rhoeff_samples[rho_offset as usize + 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 as u32, radius_offset + k as u32)
* 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 BSSRDFTrait for TabulatedBSSRDF {
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,
_bxdf: NormalizedFresnelBxDF,
) -> BSSRDFSample {
todo!()
}
}