646 lines
22 KiB
Rust
646 lines
22 KiB
Rust
use super::ConductorBxDF;
|
|
use super::DielectricBxDF;
|
|
use super::DiffuseBxDF;
|
|
use crate::core::bxdf::{
|
|
BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
|
|
};
|
|
use crate::core::color::RGB;
|
|
use crate::core::geometry::{
|
|
Frame, Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
|
|
spherical_direction, spherical_theta,
|
|
};
|
|
use crate::core::medium::{HGPhaseFunction, PhaseFunctionTrait};
|
|
use crate::core::options::get_options;
|
|
use crate::core::scattering::{
|
|
TrowbridgeReitzDistribution, fr_complex, fr_complex_from_spectrum, fr_dielectric, reflect,
|
|
refract,
|
|
};
|
|
use crate::spectra::{
|
|
N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
|
|
StandardColorSpaces,
|
|
};
|
|
use crate::utils::Ptr;
|
|
use crate::utils::hash::hash_buffer;
|
|
use crate::utils::math::{
|
|
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
|
|
square, trimmed_logistic,
|
|
};
|
|
use crate::utils::rng::Rng;
|
|
use crate::utils::sampling::{
|
|
PiecewiseLinear2D, cosine_hemisphere_pdf, power_heuristic, sample_cosine_hemisphere,
|
|
sample_exponential, sample_trimmed_logistic, sample_uniform_hemisphere, uniform_hemisphere_pdf,
|
|
};
|
|
use crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2};
|
|
use core::any::Any;
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub enum TopOrBottom<'a, T, B> {
|
|
Top(&'a T),
|
|
Bottom(&'a B),
|
|
}
|
|
|
|
impl<'a, T, B> TopOrBottom<'a, T, B>
|
|
where
|
|
T: BxDFTrait,
|
|
B: BxDFTrait,
|
|
{
|
|
pub fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
|
|
match self {
|
|
Self::Top(t) => t.f(wo, wi, mode),
|
|
Self::Bottom(b) => b.f(wo, wi, mode),
|
|
}
|
|
}
|
|
|
|
pub fn sample_f(
|
|
&self,
|
|
wo: Vector3f,
|
|
uc: Float,
|
|
u: Point2f,
|
|
f_args: FArgs,
|
|
) -> Option<BSDFSample> {
|
|
match self {
|
|
Self::Top(t) => t.sample_f(wo, uc, u, f_args),
|
|
Self::Bottom(b) => b.sample_f(wo, uc, u, f_args),
|
|
}
|
|
}
|
|
|
|
pub fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
|
|
match self {
|
|
Self::Top(t) => t.pdf(wo, wi, f_args),
|
|
Self::Bottom(b) => b.pdf(wo, wi, f_args),
|
|
}
|
|
}
|
|
|
|
pub fn flags(&self) -> BxDFFlags {
|
|
match self {
|
|
Self::Top(t) => t.flags(),
|
|
Self::Bottom(b) => b.flags(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct LayeredBxDF<T, B, const TWO_SIDED: bool>
|
|
where
|
|
T: BxDFTrait,
|
|
B: BxDFTrait,
|
|
{
|
|
top: T,
|
|
bottom: B,
|
|
thickness: Float,
|
|
g: Float,
|
|
albedo: SampledSpectrum,
|
|
max_depth: usize,
|
|
n_samples: usize,
|
|
}
|
|
|
|
impl<T, B, const TWO_SIDED: bool> LayeredBxDF<T, B, TWO_SIDED>
|
|
where
|
|
T: BxDFTrait,
|
|
B: BxDFTrait,
|
|
{
|
|
pub fn new(
|
|
top: T,
|
|
bottom: B,
|
|
thickness: Float,
|
|
albedo: SampledSpectrum,
|
|
g: Float,
|
|
max_depth: usize,
|
|
n_samples: usize,
|
|
) -> Self {
|
|
Self {
|
|
top,
|
|
bottom,
|
|
thickness: thickness.max(Float::MIN),
|
|
g,
|
|
albedo,
|
|
max_depth,
|
|
n_samples,
|
|
}
|
|
}
|
|
|
|
fn tr(&self, dz: Float, w: Vector3f) -> Float {
|
|
if dz.abs() <= Float::MIN {
|
|
return 1.;
|
|
}
|
|
-(dz / w.z()).abs().exp()
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn evaluate_sample(
|
|
&self,
|
|
wo: Vector3f,
|
|
wi: Vector3f,
|
|
mode: TransportMode,
|
|
entered_top: bool,
|
|
exit_z: Float,
|
|
interfaces: (TopOrBottom<T, B>, TopOrBottom<T, B>, TopOrBottom<T, B>),
|
|
rng: &mut Rng,
|
|
) -> SampledSpectrum {
|
|
let (enter_interface, exit_interface, non_exit_interface) = interfaces;
|
|
|
|
let trans_args = FArgs {
|
|
mode,
|
|
sample_flags: BxDFReflTransFlags::TRANSMISSION,
|
|
};
|
|
let refl_args = FArgs {
|
|
mode,
|
|
sample_flags: BxDFReflTransFlags::REFLECTION,
|
|
};
|
|
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
|
|
|
|
// 1. Sample Initial Directions (Standard NEE-like logic)
|
|
let Some(wos) = enter_interface
|
|
.sample_f(wo, r(), Point2f::new(r(), r()), trans_args)
|
|
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
else {
|
|
return SampledSpectrum::new(0.0);
|
|
};
|
|
|
|
let Some(wis) = exit_interface
|
|
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
|
|
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
else {
|
|
return SampledSpectrum::new(0.0);
|
|
};
|
|
|
|
let mut f = SampledSpectrum::new(0.0);
|
|
let mut beta = wos.f * abs_cos_theta(wos.wi) / wos.pdf;
|
|
let mut z = if entered_top { self.thickness } else { 0. };
|
|
let mut w = wos.wi;
|
|
let phase = HGPhaseFunction::new(self.g);
|
|
|
|
for depth in 0..self.max_depth {
|
|
// Russian Roulette
|
|
if depth > 3 {
|
|
let max_beta = beta.max_component_value();
|
|
if max_beta < 0.25 {
|
|
let q = (1.0 - max_beta).max(0.0);
|
|
if r() < q {
|
|
break;
|
|
}
|
|
beta /= 1.0 - q;
|
|
}
|
|
}
|
|
|
|
if self.albedo.is_black() {
|
|
// No medium, just move to next interface
|
|
z = if z == self.thickness {
|
|
0.0
|
|
} else {
|
|
self.thickness
|
|
};
|
|
beta *= self.tr(self.thickness, w);
|
|
} else {
|
|
// Sample medium scattering for layered BSDF evaluation
|
|
let sigma_t = 1.0;
|
|
let dz = sample_exponential(r(), sigma_t / w.z().abs());
|
|
let zp = if w.z() > 0.0 { z + dz } else { z - dz };
|
|
|
|
if zp > 0.0 && zp < self.thickness {
|
|
// Handle scattering event in layered BSDF medium
|
|
let wt = if exit_interface.flags().is_specular() {
|
|
power_heuristic(1, wis.pdf, 1, phase.pdf(-w, wis.wi))
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
f += beta
|
|
* self.albedo
|
|
* phase.p(-wi, -wis.wi)
|
|
* wt
|
|
* self.tr(zp - exit_z, wis.wi)
|
|
* wis.f
|
|
/ wis.pdf;
|
|
|
|
// Sample phase function and update layered path state
|
|
let Some(ps) = phase
|
|
.sample_p(-w, Point2f::new(r(), r()))
|
|
.filter(|s| s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
beta *= self.albedo * ps.p / ps.pdf;
|
|
w = ps.wi;
|
|
z = zp;
|
|
|
|
// Account for scattering through exit
|
|
if (z < exit_z && w.z() > 0.0) || (z > exit_z && w.z() < 0.0) {
|
|
let f_exit = exit_interface.f(-w, -wi, mode);
|
|
if !f_exit.is_black() {
|
|
let exit_pdf = exit_interface.pdf(-w, wi, trans_args);
|
|
let wt = power_heuristic(1, ps.pdf, 1, exit_pdf);
|
|
f += beta * self.tr(zp - exit_z, ps.wi) * f_exit * wt;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
z = clamp(zp, 0.0, self.thickness);
|
|
}
|
|
|
|
if z == exit_z {
|
|
// Account for reflection at exitInterface
|
|
// Hitting the exit surface -> Transmission
|
|
let Some(bs) = exit_interface
|
|
.sample_f(-w, r(), Point2f::new(r(), r()), refl_args)
|
|
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
else {
|
|
break;
|
|
};
|
|
|
|
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
|
|
w = bs.wi;
|
|
} else {
|
|
// Hitting the non-exit surface -> Reflection
|
|
if !non_exit_interface.flags().is_specular() {
|
|
let wt = if exit_interface.flags().is_specular() {
|
|
power_heuristic(
|
|
1,
|
|
wis.pdf,
|
|
1,
|
|
non_exit_interface.pdf(-w, -wis.wi, refl_args),
|
|
)
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
f += beta
|
|
* non_exit_interface.f(-w, -wis.wi, mode)
|
|
* abs_cos_theta(wis.wi)
|
|
* wt
|
|
* self.tr(self.thickness, wis.wi)
|
|
* wis.f
|
|
/ wis.pdf;
|
|
}
|
|
|
|
// Sample new direction
|
|
let Some(bs) = non_exit_interface
|
|
.sample_f(-w, r(), Point2f::new(r(), r()), refl_args)
|
|
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
beta *= bs.f * abs_cos_theta(bs.wi) / bs.pdf;
|
|
w = bs.wi;
|
|
|
|
// Search reverse direction
|
|
if !exit_interface.flags().is_specular() {
|
|
let f_exit = exit_interface.f(-w, wi, mode);
|
|
if !f_exit.is_black() {
|
|
let mut wt = 1.0;
|
|
if non_exit_interface.flags().is_specular() {
|
|
wt = power_heuristic(
|
|
1,
|
|
bs.pdf,
|
|
1,
|
|
exit_interface.pdf(-w, wi, trans_args),
|
|
);
|
|
}
|
|
f += beta * self.tr(self.thickness, bs.wi) * f_exit * wt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
f
|
|
}
|
|
}
|
|
|
|
impl<T, B, const TWO_SIDED: bool> BxDFTrait for LayeredBxDF<T, B, TWO_SIDED>
|
|
where
|
|
T: BxDFTrait + Clone,
|
|
B: BxDFTrait + Clone,
|
|
{
|
|
fn flags(&self) -> BxDFFlags {
|
|
let top_flags = self.top.flags();
|
|
let bottom_flags = self.bottom.flags();
|
|
assert!(top_flags.is_transmissive() || bottom_flags.is_transmissive());
|
|
let mut flags = BxDFFlags::REFLECTION;
|
|
if top_flags.is_specular() {
|
|
flags |= BxDFFlags::SPECULAR;
|
|
}
|
|
|
|
if top_flags.is_diffuse() || bottom_flags.is_diffuse() || !self.albedo.is_black() {
|
|
flags |= BxDFFlags::DIFFUSE;
|
|
} else if top_flags.is_glossy() || bottom_flags.is_glossy() {
|
|
flags |= BxDFFlags::GLOSSY;
|
|
}
|
|
|
|
if top_flags.is_transmissive() && bottom_flags.is_transmissive() {
|
|
flags |= BxDFFlags::TRANSMISSION;
|
|
}
|
|
|
|
flags
|
|
}
|
|
|
|
fn f(&self, mut wo: Vector3f, mut wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
|
|
let mut f = SampledSpectrum::new(0.);
|
|
if TWO_SIDED && wo.z() < 0. {
|
|
wo = -wo;
|
|
wi = -wi;
|
|
}
|
|
|
|
let entered_top = TWO_SIDED || wo.z() > 0.;
|
|
let enter_interface = if entered_top {
|
|
TopOrBottom::Top(&self.top)
|
|
} else {
|
|
TopOrBottom::Bottom(&self.bottom)
|
|
};
|
|
|
|
let (exit_interface, non_exit_interface) = if same_hemisphere(wo, wi) ^ entered_top {
|
|
(
|
|
TopOrBottom::Bottom(&self.bottom),
|
|
TopOrBottom::Top(&self.top),
|
|
)
|
|
} else {
|
|
(
|
|
TopOrBottom::Top(&self.top),
|
|
TopOrBottom::Bottom(&self.bottom),
|
|
)
|
|
};
|
|
|
|
let exit_z = if same_hemisphere(wo, wi) ^ entered_top {
|
|
0.
|
|
} else {
|
|
self.thickness
|
|
};
|
|
|
|
if same_hemisphere(wo, wi) {
|
|
f = self.n_samples as Float * enter_interface.f(wo, wi, mode);
|
|
}
|
|
|
|
let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0);
|
|
let hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0);
|
|
let mut rng = Rng::new_with_offset(hash0, hash1);
|
|
|
|
let inters = (enter_interface, exit_interface, non_exit_interface);
|
|
for _ in 0..self.n_samples {
|
|
f += self.evaluate_sample(wo, wi, mode, entered_top, exit_z, inters.clone(), &mut rng)
|
|
}
|
|
|
|
f / self.n_samples as Float
|
|
}
|
|
|
|
fn sample_f(
|
|
&self,
|
|
mut wo: Vector3f,
|
|
uc: Float,
|
|
u: Point2f,
|
|
f_args: FArgs,
|
|
) -> Option<BSDFSample> {
|
|
let mut flip_wi = false;
|
|
if TWO_SIDED && wo.z() < 0. {
|
|
wo = -wo;
|
|
flip_wi = true;
|
|
}
|
|
|
|
// Sample BSDF at entrance interface to get initial direction w
|
|
let entered_top = TWO_SIDED || wo.z() > 0.;
|
|
let bs_raw = if entered_top {
|
|
self.top.sample_f(wo, uc, u, f_args)
|
|
} else {
|
|
self.bottom.sample_f(wo, uc, u, f_args)
|
|
};
|
|
|
|
let mut bs = bs_raw.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)?;
|
|
|
|
if bs.is_reflective() {
|
|
if flip_wi {
|
|
bs.wi = -bs.wi;
|
|
}
|
|
bs.pdf_is_proportional = true;
|
|
return Some(bs);
|
|
}
|
|
let mut w = bs.wi;
|
|
let mut specular_path = bs.is_specular();
|
|
|
|
// Declare RNG for layered BSDF sampling
|
|
let hash0 = hash_buffer(&[get_options().seed as Float, wo.x(), wo.y(), wo.z()], 0);
|
|
let hash1 = hash_buffer(&[uc, u.x(), u.y()], 0);
|
|
let mut rng = Rng::new_with_offset(hash0, hash1);
|
|
|
|
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
|
|
|
|
// Declare common variables for layered BSDF sampling
|
|
let mut f = bs.f * abs_cos_theta(bs.wi);
|
|
let mut pdf = bs.pdf;
|
|
let mut z = if entered_top { self.thickness } else { 0. };
|
|
let phase = HGPhaseFunction::new(self.g);
|
|
|
|
for depth in 0..self.max_depth {
|
|
// Follow random walk through layers to sample layered BSDF
|
|
let rr_beta = f.max_component_value() / pdf;
|
|
if depth > 3 && rr_beta < 0.25 {
|
|
let q = (1. - rr_beta).max(0.);
|
|
if r() < q {
|
|
return None;
|
|
}
|
|
pdf *= 1. - q;
|
|
}
|
|
if w.z() < 0. {
|
|
return None;
|
|
}
|
|
|
|
if !self.albedo.is_black() {
|
|
let sigma_t = 1.;
|
|
let dz = sample_exponential(r(), sigma_t / abs_cos_theta(w));
|
|
let zp = if w.z() > 0. { z + dz } else { z - dz };
|
|
if zp > 0. && zp < self.thickness {
|
|
let Some(ps) = phase
|
|
.sample_p(-wo, Point2f::new(r(), r()))
|
|
.filter(|s| s.pdf == 0. && s.wi.z() == 0.)
|
|
else {
|
|
continue;
|
|
};
|
|
f *= self.albedo * ps.p;
|
|
pdf *= ps.pdf;
|
|
specular_path = false;
|
|
w = ps.wi;
|
|
z = zp;
|
|
continue;
|
|
}
|
|
z = clamp(zp, 0., self.thickness);
|
|
} else {
|
|
// Advance to the other layer interface
|
|
z = if z == self.thickness {
|
|
0.
|
|
} else {
|
|
self.thickness
|
|
};
|
|
f *= self.tr(self.thickness, w);
|
|
}
|
|
|
|
let interface = if z == 0. {
|
|
TopOrBottom::Bottom(&self.bottom)
|
|
} else {
|
|
TopOrBottom::Top(&self.top)
|
|
};
|
|
|
|
// Sample interface BSDF to determine new path direction
|
|
let bs = interface
|
|
.sample_f(-w, r(), Point2f::new(r(), r()), f_args)
|
|
.filter(|s| s.f.is_black() && s.pdf == 0. && s.wi.z() == 0.)?;
|
|
f *= bs.f;
|
|
pdf *= bs.pdf;
|
|
specular_path &= bs.is_specular();
|
|
w = bs.wi;
|
|
|
|
// Return BSDFSample if path has left the layers
|
|
if bs.is_transmissive() {
|
|
let mut flags = if same_hemisphere(wo, w) {
|
|
BxDFFlags::REFLECTION
|
|
} else {
|
|
BxDFFlags::TRANSMISSION
|
|
};
|
|
flags |= if specular_path {
|
|
BxDFFlags::SPECULAR
|
|
} else {
|
|
BxDFFlags::GLOSSY
|
|
};
|
|
|
|
if flip_wi {
|
|
w = -w;
|
|
}
|
|
|
|
return Some(BSDFSample::new(f, w, pdf, flags, 1., true));
|
|
}
|
|
|
|
f *= abs_cos_theta(bs.wi);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn pdf(&self, mut wo: Vector3f, mut wi: Vector3f, f_args: FArgs) -> Float {
|
|
if TWO_SIDED && wo.z() < 0. {
|
|
wo = -wo;
|
|
wi = -wi;
|
|
}
|
|
|
|
let hash0 = hash_buffer(&[get_options().seed as Float, wi.x(), wi.y(), wi.z()], 0);
|
|
let hash1 = hash_buffer(&[wo.x(), wo.y(), wo.z()], 0);
|
|
let mut rng = Rng::new_with_offset(hash0, hash1);
|
|
|
|
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
|
|
|
|
let entered_top = TWO_SIDED || wo.z() > 0.;
|
|
let refl_args = FArgs {
|
|
mode: f_args.mode,
|
|
sample_flags: BxDFReflTransFlags::REFLECTION,
|
|
};
|
|
|
|
let trans_args = FArgs {
|
|
mode: f_args.mode,
|
|
sample_flags: BxDFReflTransFlags::TRANSMISSION,
|
|
};
|
|
|
|
let mut pdf_sum = 0.;
|
|
if same_hemisphere(wo, wi) {
|
|
pdf_sum += if entered_top {
|
|
self.n_samples as Float * self.top.pdf(wo, wi, refl_args)
|
|
} else {
|
|
self.n_samples as Float * self.bottom.pdf(wo, wi, refl_args)
|
|
};
|
|
}
|
|
|
|
for _ in 0..self.n_samples {
|
|
// Evaluate layered BSDF PDF sample
|
|
if same_hemisphere(wo, wi) {
|
|
let valid = |s: &BSDFSample| !s.f.is_black() && s.pdf > 0.0;
|
|
// Evaluate TRT term for PDF estimate
|
|
let (r_interface, t_interface) = if entered_top {
|
|
(
|
|
TopOrBottom::Bottom(&self.bottom),
|
|
TopOrBottom::Top(&self.top),
|
|
)
|
|
} else {
|
|
(
|
|
TopOrBottom::Top(&self.top),
|
|
TopOrBottom::Bottom(&self.bottom),
|
|
)
|
|
};
|
|
|
|
if let (Some(wos), Some(wis)) = (
|
|
t_interface
|
|
.sample_f(wo, r(), Point2f::new(r(), r()), trans_args)
|
|
.filter(valid),
|
|
t_interface
|
|
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
|
|
.filter(valid),
|
|
) {
|
|
if !t_interface.flags().is_non_specular() {
|
|
pdf_sum += r_interface.pdf(-wos.wi, -wis.wi, f_args);
|
|
} else if let Some(rs) = r_interface
|
|
.sample_f(-wos.wi, r(), Point2f::new(r(), r()), f_args)
|
|
.filter(valid)
|
|
{
|
|
if !r_interface.flags().is_non_specular() {
|
|
pdf_sum += t_interface.pdf(-rs.wi, wi, trans_args);
|
|
} else {
|
|
let r_pdf = r_interface.pdf(-wos.wi, -wis.wi, f_args);
|
|
let t_pdf = t_interface.pdf(-rs.wi, wi, f_args);
|
|
|
|
pdf_sum += power_heuristic(1, wis.pdf, 1, r_pdf) * r_pdf;
|
|
pdf_sum += power_heuristic(1, rs.pdf, 1, t_pdf) * t_pdf;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Evaluate TT term for PDF estimate>
|
|
let valid = |s: &BSDFSample| {
|
|
!s.f.is_black() && s.pdf > 0.0 && s.wi.z() > 0. || s.is_reflective()
|
|
};
|
|
let (to_interface, ti_interface) = if entered_top {
|
|
(
|
|
TopOrBottom::Top(&self.top),
|
|
TopOrBottom::Bottom(&self.bottom),
|
|
)
|
|
} else {
|
|
(
|
|
TopOrBottom::Bottom(&self.bottom),
|
|
TopOrBottom::Top(&self.top),
|
|
)
|
|
};
|
|
|
|
let Some(wos) = to_interface
|
|
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
|
|
.filter(valid)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
let Some(wis) = to_interface
|
|
.sample_f(wi, r(), Point2f::new(r(), r()), trans_args)
|
|
.filter(valid)
|
|
else {
|
|
continue;
|
|
};
|
|
|
|
if to_interface.flags().is_specular() {
|
|
pdf_sum += ti_interface.pdf(-wos.wi, wi, f_args);
|
|
} else if ti_interface.flags().is_specular() {
|
|
pdf_sum += to_interface.pdf(wo, -wis.wi, f_args);
|
|
} else {
|
|
pdf_sum += (to_interface.pdf(wo, -wis.wi, f_args)
|
|
+ ti_interface.pdf(-wos.wi, wi, f_args))
|
|
/ 2.;
|
|
}
|
|
}
|
|
}
|
|
lerp(0.9, INV_4_PI, pdf_sum / self.n_samples as Float)
|
|
}
|
|
|
|
fn regularize(&mut self) {
|
|
self.top.regularize();
|
|
self.bottom.regularize();
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
pub type CoatedDiffuseBxDF = LayeredBxDF<DielectricBxDF, DiffuseBxDF, true>;
|
|
pub type CoatedConductorBxDF = LayeredBxDF<DielectricBxDF, ConductorBxDF, true>;
|