346 lines
10 KiB
Rust
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!()
|
|
}
|
|
}
|