use crate::{
ggx::GGX,
utils::{self, FloatExt},
RgbD, SampleIncomingResponse, SampleOutgoingResponse, Vec3d, BSDF,
};
struct BsdfPdfResult {
bsdf: RgbD,
pdf: f64,
}
pub struct RoughGlass {
pub ggx: GGX,
pub ior: f64,
}
impl RoughGlass {
fn get_ior_of(omega: Vec3d, ior_above: f64, ior_below: f64) -> f64 {
if omega.z > 0.0 {
ior_above
} else {
ior_below
}
}
fn eval_get_pdf_reflect(&self, omega_i: Vec3d, omega_o: Vec3d) -> BsdfPdfResult {
let m_opt = (omega_i + omega_o).try_normalize();
let m = match m_opt {
None => {
return BsdfPdfResult {
bsdf: RgbD::ZERO,
pdf: 0.,
};
}
Some(m) => m * omega_o.z.signum(),
};
let ior_o = Self::get_ior_of(omega_o, 1.0, self.ior);
let ior_t = Self::get_ior_of(-omega_o, 1.0, self.ior);
let f = utils::fresnel(omega_o, m, ior_o, ior_t);
let pdf = f * utils::sample_ggx_vndf_reflection_lobe_pdf(
self.ggx,
omega_o * omega_o.z.signum(),
m,
);
if !GGX::g2_d_satisfied(omega_i, omega_o, m) || omega_o.z * omega_i.z < 0.0 {
return BsdfPdfResult {
bsdf: RgbD::ZERO,
pdf,
};
}
let g = self.ggx.geometric(omega_i, omega_o, m);
let d = self.ggx.ndf(m);
let result = g * d * f / (4.0 * omega_i.z * omega_o.z);
BsdfPdfResult {
bsdf: RgbD::splat(result),
pdf,
}
}
fn eval_get_pdf_refract(&self, omega_i: Vec3d, omega_o: Vec3d) -> BsdfPdfResult {
let ior_o = Self::get_ior_of(omega_o, 1.0, self.ior);
let ior_t = Self::get_ior_of(-omega_o, 1.0, self.ior);
let m_opt =
utils::refract_normal(omega_i, omega_o, ior_t, ior_o).and_then(Vec3d::try_normalize);
let m = match m_opt {
None => {
return BsdfPdfResult {
bsdf: RgbD::ZERO,
pdf: 0.0,
}
}
Some(m) if self.ior > 1.0 => m,
Some(m) => -m,
};
if m.z < 0.0 {
return BsdfPdfResult {
bsdf: RgbD::ZERO,
pdf: 0.0,
};
}
let fresnel = utils::fresnel(omega_o, m, ior_o, ior_t);
let vndf = self.ggx.vndf(omega_o * omega_o.z.signum(), m);
#[allow(clippy::suboptimal_flops)]
let jacobian = ior_t.sq() * omega_i.dot(m).abs()
/ (ior_t * m.dot(omega_i) + ior_o * m.dot(omega_o)).sq();
let vndf_pdf = (1.0 - fresnel) * vndf * jacobian;
assert!(vndf_pdf >= 0.0);
if !GGX::g2_d_satisfied(omega_i, omega_o, m) || omega_i.z * omega_o.z > 0.0 {
return BsdfPdfResult {
bsdf: RgbD::ZERO,
pdf: vndf_pdf,
};
}
let masking_shadowing = self.ggx.geometric(omega_i, omega_o, m);
let ndf = self.ggx.ndf(m);
#[allow(clippy::suboptimal_flops)]
let btdf_p = ior_o.sq() / (ior_t * omega_i.dot(m) + ior_o * omega_o.dot(m)).sq();
let correction_factors =
((omega_i.dot(m) / omega_i.z) * (omega_o.dot(m) / omega_o.z)).abs();
let radiance_correction = ior_t / ior_o;
let bsdf = masking_shadowing
* ndf
* (1.0 - fresnel)
* btdf_p
* correction_factors
* radiance_correction.sq();
BsdfPdfResult {
bsdf: RgbD::splat(bsdf),
pdf: vndf_pdf,
}
}
fn bsdf_get_pdf(&self, omega_i: Vec3d, omega_o: Vec3d) -> BsdfPdfResult {
let reflect = self.eval_get_pdf_reflect(omega_i, omega_o);
let refract = self.eval_get_pdf_refract(omega_i, omega_o);
BsdfPdfResult {
bsdf: reflect.bsdf + refract.bsdf,
pdf: reflect.pdf + refract.pdf,
}
}
}
impl BSDF for RoughGlass {
fn evaluate(&self, omega_o: Vec3d, omega_i: Vec3d) -> RgbD {
self.bsdf_get_pdf(omega_i, omega_o).bsdf
}
fn sample_incoming(&self, omega_o: Vec3d, rdf: Vec3d) -> SampleIncomingResponse {
let ior_o = Self::get_ior_of(omega_o, 1.0, self.ior);
let ior_t = Self::get_ior_of(-omega_o, 1.0, self.ior);
let m = self
.ggx
.sample_vndf(omega_o * omega_o.z.signum(), rdf.x, rdf.y);
let fresnel = utils::fresnel(omega_o, m, ior_o, ior_t);
let omega_i = if rdf.z <= fresnel {
utils::reflect(m, omega_o)
} else {
utils::refract_good(omega_o, m, ior_o, ior_t)
.unwrap()
.normalize()
};
let BsdfPdfResult { bsdf, pdf } = self.bsdf_get_pdf(omega_i, omega_o);
SampleIncomingResponse {
omega_i,
bsdf,
emission: RgbD::ZERO,
pdf,
}
}
fn sample_outgoing(&self, omega_i: Vec3d, rdf: Vec3d) -> SampleOutgoingResponse {
assert!(omega_i.is_normalized());
let response = self.sample_incoming(omega_i, rdf);
let omega_o = response.omega_i;
let ior_i = if omega_i.z > 0.0 { 1.0 } else { self.ior };
let ior_o = if omega_o.z > 0.0 { 1.0 } else { self.ior };
SampleOutgoingResponse {
omega_o,
bsdf: response.bsdf * ior_i.sq() / ior_o.sq(),
adjoint_bsdf: response.bsdf,
pdf: response.pdf,
}
}
fn sample_incoming_pdf(&self, omega_o: Vec3d, omega_i: Vec3d) -> f64 {
self.bsdf_get_pdf(omega_i, omega_o).pdf
}
fn base_color(&self, _omega_o: Vec3d) -> RgbD {
RgbD::ZERO
}
}
#[cfg(test)]
impl crate::core::TransmissiveBsdf for RoughGlass {
fn ior(&self) -> f64 {
self.ior
}
}
#[cfg(test)]
#[allow(clippy::cast_possible_truncation)]
mod tests {
use crate::{ggx::GGX, test_utils};
const SMOOT_RG: RoughGlass = RoughGlass {
ior: 1.45,
ggx: GGX {
alpha_x: 0.05 * 0.05,
alpha_y: 0.05 * 0.05,
},
};
const ROUGH_RG: RoughGlass = RoughGlass {
ior: 1.45,
ggx: GGX {
alpha_x: 0.3 * 0.3,
alpha_y: 0.4 * 0.4,
},
};
use super::RoughGlass;
#[test]
fn sample_eval() {
test_utils::test_bsdf_sample_eval(&ROUGH_RG);
test_utils::test_bsdf_sample_eval_adjoint(&ROUGH_RG);
}
#[test]
fn reciprocity() {
test_utils::test_bsdf_reciprocity_glass(&ROUGH_RG);
}
#[test]
fn pdf_integral() {
test_utils::test_integrate_inverse_pdf(&ROUGH_RG);
}
#[test]
fn white_furnace() {
test_utils::test_white_furnace(&SMOOT_RG, 0.05);
}
#[test]
fn white_furnace_adjoint() {
test_utils::test_white_furnace_adjoint(&SMOOT_RG, 0.05);
}
}