Everything is broken

This commit is contained in:
pingu 2025-12-04 12:55:06 +00:00
parent 96e437921f
commit d42437e860
18 changed files with 1617 additions and 312 deletions

286
src/core/bssrdf.rs Normal file
View file

@ -0,0 +1,286 @@
use crate::core::bxdf::BSDF;
use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::{Float, PI};
use crate::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::math::{catmull_rom_weights, square};
use crate::utils::sampling::sample_catmull_rom_2d;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct BSSRDFSample {
sp: SampledSpectrum,
pdf: SampledSpectrum,
sw: BSDF,
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,
}
}
}
#[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 {
p0: Point3f,
p1: Point3f,
}
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
fn probe_intersection_to_sample(si: &SubsurfaceInteraction) -> BSSRDFSample;
}
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
// We use 'j' for the offset calculation and 'rho_weight' for calculation
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(_si: &SubsurfaceInteraction) -> BSSRDFSample {
todo!()
}
}

View file

@ -173,6 +173,12 @@ pub struct DiffuseBxDF {
pub r: SampledSpectrum,
}
impl DiffuseBxDF {
pub fn new(r: SampledSpectrum) -> Self {
Self { r }
}
}
#[derive(Debug, Clone)]
pub struct DiffuseTransmissionBxDF;
@ -407,16 +413,16 @@ impl Default for FArgs {
pub trait BxDFTrait: Any + Send + Sync + std::fmt::Debug {
fn flags(&self) -> BxDFFlags;
fn f(&self, wo: &Vector3f, wi: &Vector3f, mode: TransportMode) -> SampledSpectrum;
fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum;
fn sample_f(&self, wo: &Vector3f, uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample>;
fn sample_f(&self, wo: Vector3f, uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample>;
fn pdf(&self, wo: &Vector3f, wi: &Vector3f, f_args: FArgs) -> Float;
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float;
fn rho_wo(&self, wo: Vector3f, uc: &[Float], u2: &[Point2f]) -> SampledSpectrum {
let mut r = SampledSpectrum::new(0.);
for i in 0..uc.len() {
if let Some(bs) = self.sample_f(&wo, uc[i], u2[i], FArgs::default()) {
if let Some(bs) = self.sample_f(wo, uc[i], u2[i], FArgs::default()) {
r += bs.f * abs_cos_theta(bs.wi) / bs.pdf;
}
}
@ -431,7 +437,7 @@ pub trait BxDFTrait: Any + Send + Sync + std::fmt::Debug {
continue;
}
let pdfo = uniform_hemisphere_pdf();
if let Some(bs) = self.sample_f(&wo, uc[i], u2[i], FArgs::default()) {
if let Some(bs) = self.sample_f(wo, uc[i], u2[i], FArgs::default()) {
r += bs.f * abs_cos_theta(bs.wi) * abs_cos_theta(wo) / (pdfo * bs.pdf);
}
}
@ -448,19 +454,19 @@ pub trait BxDFTrait: Any + Send + Sync + std::fmt::Debug {
#[derive(Debug)]
pub struct EmptyBxDF;
impl BxDFTrait for EmptyBxDF {
fn f(&self, _wo: &Vector3f, _wi: &Vector3f, _mode: TransportMode) -> SampledSpectrum {
fn f(&self, _wo: Vector3f, _wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
SampledSpectrum::default()
}
fn sample_f(
&self,
_wo: &Vector3f,
_wo: Vector3f,
_u: Float,
_u2: Point2f,
_f_args: FArgs,
) -> Option<BSDFSample> {
None
}
fn pdf(&self, _wo: &Vector3f, _wi: &Vector3f, _f_args: FArgs) -> Float {
fn pdf(&self, _wo: Vector3f, _wi: Vector3f, _f_args: FArgs) -> Float {
0.0
}
fn flags(&self) -> BxDFFlags {
@ -486,23 +492,23 @@ pub enum BxDF {
// DiffuseTransmission(DiffuseTransmissionBxDF),
}
#[derive(Debug, Clone)]
pub struct BSDF {
bxdf: Arc<RwLock<dyn BxDFTrait>>,
#[derive(Debug)]
pub struct BSDF<'a> {
bxdf: Option<&'a mut dyn BxDFTrait>,
shading_frame: Frame,
}
impl Default for BSDF {
impl<'a> Default for BSDF<'a> {
fn default() -> Self {
Self {
bxdf: Arc::new(RwLock::new(EmptyBxDF)),
bxdf: None,
shading_frame: Frame::default(),
}
}
}
impl BSDF {
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Arc<RwLock<dyn BxDFTrait>>) -> Self {
impl<'a> BSDF<'a> {
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Option<&'a mut dyn BxDFTrait>) -> Self {
Self {
bxdf,
shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)),
@ -510,13 +516,14 @@ impl BSDF {
}
pub fn is_valid(&self) -> bool {
let guard = self.bxdf.read().unwrap();
guard.as_any().downcast_ref::<EmptyBxDF>().is_none()
self.bxdf.is_some()
}
pub fn flags(&self) -> BxDFFlags {
let guard = self.bxdf.read().unwrap();
guard.flags()
match &self.bxdf {
Some(b) => b.flags(),
None => BxDFFlags::empty(), // or Transmissive if it's invisible
}
}
pub fn render_to_local(&self, v: Vector3f) -> Vector3f {
@ -528,134 +535,92 @@ impl BSDF {
}
pub fn f(
&self,
wo_render: &Vector3f,
wi_render: &Vector3f,
mode: TransportMode,
) -> SampledSpectrum {
let wo = self.render_to_local(*wo_render);
if wo.z() == 0. {
return SampledSpectrum::default();
}
let wi = self.render_to_local(*wi_render);
let guard = self.bxdf.read().unwrap();
guard.f(&wo, &wi, mode)
}
pub fn sample_f(
&self,
wo_render: &Vector3f,
u: Float,
u2: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
let wo = self.render_to_local(*wo_render);
let sampling_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());
let guard = self.bxdf.read().unwrap();
if wo.z() == 0. || !(guard.flags().contains(sampling_flags)) {
return None;
}
if let Some(mut bs) = guard.sample_f(&wo, u, u2, f_args)
&& bs.pdf > 0.0
&& bs.wi.z() != 0.0
{
bs.wi = self.local_to_render(bs.wi);
return Some(bs);
}
None
}
pub fn pdf(&self, wo_render: &Vector3f, wi_render: &Vector3f, f_args: FArgs) -> Float {
let wo = self.render_to_local(*wo_render);
if wo.z() == 0.0 {
return 0.;
}
let wi = self.render_to_local(*wi_render);
let guard = self.bxdf.read().unwrap();
guard.pdf(&wo, &wi, f_args)
}
pub fn f_specific<B: BxDFTrait>(
&self,
wo_render: Vector3f,
wi_render: Vector3f,
mode: TransportMode,
) -> SampledSpectrum {
let bxdf = match &self.bxdf {
Some(b) => b,
None => return SampledSpectrum::default(),
};
let wi = self.render_to_local(wi_render);
let wo = self.render_to_local(wo_render);
if wo.z() == 0.0 {
if wo.z() == 0.0 || wi.z() == 0.0 {
return SampledSpectrum::default();
}
let wi = self.render_to_local(wi_render);
let guard = self.bxdf.read().unwrap();
if let Some(specific_bxdf) = guard.as_any().downcast_ref::<B>() {
specific_bxdf.f(&wo, &wi, mode)
} else {
SampledSpectrum::default()
}
bxdf.f(wo, wi, mode)
}
pub fn sample_f_specific<B: BxDFTrait>(
pub fn sample_f(
&self,
wo_render: &Vector3f,
wo_render: Vector3f,
u: Float,
u2: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
let wo = self.render_to_local(*wo_render);
let sampling_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());
let guard = self.bxdf.read().unwrap();
let bxdf = self.bxdf.as_ref()?;
if wo.z() == 0. || !guard.flags().contains(sampling_flags) {
let sampling_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());
let wo = self.render_to_local(wo_render);
if wo.z() == 0.0 || !bxdf.flags().contains(sampling_flags) {
return None;
}
guard
.as_any()
.downcast_ref::<B>()
.and_then(|specific_bxdf| specific_bxdf.sample_f(&wo, u, u2, f_args))
.filter(|bs| bs.pdf > 0.0 && bs.wi.z() != 0.0)
.map(|mut bs| {
bs.wi = self.local_to_render(bs.wi);
bs
})
let mut sample = bxdf.sample_f(wo, u, u2, f_args)?;
if sample.pdf > 0.0 && sample.wi.z() != 0.0 {
sample.wi = self.local_to_render(sample.wi);
return Some(sample);
}
None
}
pub fn pdf_specific<B: BxDFTrait>(
&self,
wo_render: &Vector3f,
wi_render: &Vector3f,
f_args: FArgs,
) -> Float {
let wo = self.render_to_local(*wo_render);
if wo.z() == 0.0 {
return 0.;
}
let wi = self.render_to_local(*wi_render);
let guard = self.bxdf.read().unwrap();
pub fn pdf(&self, wo_render: Vector3f, wi_render: Vector3f, f_args: FArgs) -> Float {
let bxdf = match &self.bxdf {
Some(b) => b,
None => return 0.0,
};
if let Some(specific_bxdf) = guard.as_any().downcast_ref::<B>() {
specific_bxdf.pdf(&wo, &wi, f_args)
} else {
0.
let sample_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits());
let wo = self.render_to_local(wo_render);
let wi = self.render_to_local(wi_render);
if wo.z() == 0.0 || !bxdf.flags().contains(sample_flags) {
return 0.0;
}
bxdf.pdf(wo, wi, f_args)
}
pub fn rho_u(&self, u1: &[Point2f], uc: &[Float], u2: &[Point2f]) -> SampledSpectrum {
let guard = self.bxdf.read().unwrap();
guard.rho_u(u1, uc, u2)
let bxdf = match &self.bxdf {
Some(b) => b,
None => return SampledSpectrum::default(),
};
bxdf.rho_u(u1, uc, u2)
}
pub fn rho_wo(&self, wo_render: Vector3f, uc: &[Float], u: &[Point2f]) -> SampledSpectrum {
let bxdf = match &self.bxdf {
Some(b) => b,
None => return SampledSpectrum::default(),
};
let wo = self.render_to_local(wo_render);
let guard = self.bxdf.read().unwrap();
guard.rho_wo(wo, uc, u)
bxdf.rho_wo(wo, uc, u)
}
pub fn regularize(&mut self) {
let mut guard = self.bxdf.write().unwrap();
guard.regularize()
if let Some(bxdf) = &mut self.bxdf {
bxdf.regularize()
}
}
}
@ -668,14 +633,14 @@ impl BxDFTrait for DiffuseBxDF {
}
}
fn f(&self, wo: &Vector3f, wi: &Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(*wo, *wi) {
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::new(0.);
}
self.r * INV_PI
}
fn sample_f(&self, wo: &Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
@ -696,13 +661,13 @@ impl BxDFTrait for DiffuseBxDF {
Some(bsdf)
}
fn pdf(&self, wo: &Vector3f, wi: &Vector3f, f_args: FArgs) -> Float {
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::ALL.bits());
if !f_args.sample_flags.contains(reflection_flags) || !same_hemisphere(*wo, *wi) {
if !f_args.sample_flags.contains(reflection_flags) || !same_hemisphere(wo, wi) {
return 0.;
}
cosine_hemisphere_pdf(abs_cos_theta(*wi))
cosine_hemisphere_pdf(abs_cos_theta(wi))
}
fn as_any(&self) -> &dyn Any {
@ -718,7 +683,7 @@ impl BxDFTrait for ConductorBxDF {
BxDFFlags::GLOSSY_REFLECTION
}
}
fn sample_f(&self, wo: &Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
@ -744,22 +709,22 @@ impl BxDFTrait for ConductorBxDF {
if wo.z() == 0. {
return None;
}
let wm = self.mf_distrib.sample_wm(*wo, u);
let wi = reflect(*wo, wm.into());
if !same_hemisphere(*wo, wi) {
let wm = self.mf_distrib.sample_wm(wo, u);
let wi = reflect(wo, wm.into());
if !same_hemisphere(wo, wi) {
return None;
}
let pdf = self.mf_distrib.pdf(*wo, wm) / (4. * (*wo).dot(wm).abs());
let pdf = self.mf_distrib.pdf(wo, wm) / (4. * wo.dot(wm).abs());
let cos_theta_o = abs_cos_theta(*wo);
let cos_theta_o = abs_cos_theta(wo);
let cos_theta_i = abs_cos_theta(wi);
if cos_theta_i == 0. || cos_theta_o == 0. {
return None;
}
let f_spectrum = fr_complex_from_spectrum((*wo).dot(wi).abs(), self.eta, self.k);
let f = self.mf_distrib.d(wm) * f_spectrum * self.mf_distrib.g(*wo, wi)
let f_spectrum = fr_complex_from_spectrum(wo.dot(wi).abs(), self.eta, self.k);
let f = self.mf_distrib.d(wm) * f_spectrum * self.mf_distrib.g(wo, wi)
/ (4. * cos_theta_i * cos_theta_o);
let bsdf = BSDFSample {
@ -773,49 +738,49 @@ impl BxDFTrait for ConductorBxDF {
Some(bsdf)
}
fn f(&self, wo: &Vector3f, wi: &Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(*wo, *wi) {
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::default();
}
if self.mf_distrib.effectively_smooth() {
return SampledSpectrum::default();
}
let cos_theta_o = abs_cos_theta(*wo);
let cos_theta_i = abs_cos_theta(*wi);
let cos_theta_o = abs_cos_theta(wo);
let cos_theta_i = abs_cos_theta(wi);
if cos_theta_i == 0. || cos_theta_o == 0. {
return SampledSpectrum::new(0.);
}
let wm = *wi + *wo;
let wm = wi + wo;
if wm.norm_squared() == 0. {
return SampledSpectrum::new(0.);
}
let wm_norm = wm.normalize();
let f_spectrum = fr_complex_from_spectrum((*wo).dot(wm).abs(), self.eta, self.k);
self.mf_distrib.d(wm_norm) * f_spectrum * self.mf_distrib.g(*wo, *wi)
let f_spectrum = fr_complex_from_spectrum(wo.dot(wm).abs(), self.eta, self.k);
self.mf_distrib.d(wm_norm) * f_spectrum * self.mf_distrib.g(wo, wi)
/ (4. * cos_theta_i * cos_theta_o)
}
fn pdf(&self, wo: &Vector3f, wi: &Vector3f, f_args: FArgs) -> Float {
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return 0.;
}
if !same_hemisphere(*wo, *wi) {
if !same_hemisphere(wo, wi) {
return 0.;
}
if self.mf_distrib.effectively_smooth() {
return 0.;
}
let wm = *wo + *wi;
let wm = wo + wi;
if wm.norm_squared() == 0. {
return 0.;
}
let wm_corr = Normal3f::new(0., 0., 1.).face_forward(wm);
self.mf_distrib.pdf(*wo, wm_corr.into()) / (4. * (*wo).dot(wm).abs())
self.mf_distrib.pdf(wo, wm_corr.into()) / (4. * wo.dot(wm).abs())
}
fn regularize(&mut self) {
@ -842,14 +807,14 @@ impl BxDFTrait for DielectricBxDF {
}
}
fn f(&self, wo: &Vector3f, wi: &Vector3f, mode: TransportMode) -> SampledSpectrum {
fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
if self.eta == 1. || self.mf_distrib.effectively_smooth() {
return SampledSpectrum::new(0.);
}
// Generalized half vector wm
let cos_theta_o = cos_theta(*wo);
let cos_theta_i = cos_theta(*wi);
let cos_theta_o = cos_theta(wo);
let cos_theta_i = cos_theta(wi);
let reflect = cos_theta_i * cos_theta_o > 0.;
let mut etap = 1.;
@ -861,31 +826,30 @@ impl BxDFTrait for DielectricBxDF {
};
}
let wm_orig = *wi * etap + *wo;
let wm_orig = wi * etap + wo;
if cos_theta_i == 0. || cos_theta_o == 0. || wm_orig.norm_squared() == 0. {
return SampledSpectrum::new(0.);
}
let wm = Normal3f::new(0., 0., 1.).face_forward(wm_orig.normalize());
if (*wi).dot(wm.into()) * cos_theta_i < 0. || (*wo).dot(wm.into()) * cos_theta_o < 0. {
if wi.dot(wm.into()) * cos_theta_i < 0. || wo.dot(wm.into()) * cos_theta_o < 0. {
return SampledSpectrum::new(0.);
}
let fr = fr_dielectric((*wo).dot(wm.into()), self.eta);
let fr = fr_dielectric(wo.dot(wm.into()), self.eta);
if reflect {
SampledSpectrum::new(
self.mf_distrib.d(wm.into()) * self.mf_distrib.g(*wo, *wi) * fr
self.mf_distrib.d(wm.into()) * self.mf_distrib.g(wo, wi) * fr
/ (4. * cos_theta_i * cos_theta_o).abs(),
)
} else {
let denom = square((*wi).dot(wm.into()) + (*wo).dot(wm.into()) / etap)
* cos_theta_i
* cos_theta_o;
let denom =
square(wi.dot(wm.into()) + wo.dot(wm.into()) / etap) * cos_theta_i * cos_theta_o;
let mut ft = self.mf_distrib.d(wm.into())
* (1. - fr)
* self.mf_distrib.g(*wo, *wi)
* ((*wi).dot(wm.into()) * (*wo).dot(wm.into()) / denom).abs();
* self.mf_distrib.g(wo, wi)
* (wi.dot(wm.into()) * wo.dot(wm.into()) / denom).abs();
if mode == TransportMode::Radiance {
ft /= square(etap)
}
@ -894,13 +858,13 @@ impl BxDFTrait for DielectricBxDF {
}
}
fn pdf(&self, wo: &Vector3f, wi: &Vector3f, f_args: FArgs) -> Float {
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
if self.eta == 1. || self.mf_distrib.effectively_smooth() {
return 0.;
}
let cos_theta_o = cos_theta(*wo);
let cos_theta_i = cos_theta(*wi);
let cos_theta_o = cos_theta(wo);
let cos_theta_i = cos_theta(wi);
let reflect = cos_theta_i * cos_theta_o > 0.;
let mut etap = 1.;
@ -912,7 +876,7 @@ impl BxDFTrait for DielectricBxDF {
};
}
let wm_orig = *wi * etap + *wo;
let wm_orig = wi * etap + wo;
if cos_theta_i == 0. || cos_theta_o == 0. || wm_orig.norm_squared() == 0. {
return 0.;
@ -920,11 +884,11 @@ impl BxDFTrait for DielectricBxDF {
let wm = Normal3f::new(0., 0., 1.).face_forward(wm_orig.normalize());
// Discard backfacing microfacets
if (*wi).dot(wm.into()) * cos_theta_i < 0. || (*wo).dot(wm.into()) * cos_theta_o < 0. {
if wi.dot(wm.into()) * cos_theta_i < 0. || wo.dot(wm.into()) * cos_theta_o < 0. {
return 0.;
}
let r = fr_dielectric((*wo).dot(wm.into()), self.eta);
let r = fr_dielectric(wo.dot(wm.into()), self.eta);
let t = 1. - r;
let mut pr = r;
let mut pt = t;
@ -944,19 +908,19 @@ impl BxDFTrait for DielectricBxDF {
if reflect {
self.mf_distrib.pdf(
*wo,
Vector3f::from(wm) / (4. * (*wo).dot(wm.into()).abs()) * pr / (pt + pr),
wo,
Vector3f::from(wm) / (4. * wo.dot(wm.into()).abs()) * pr / (pt + pr),
)
} else {
let denom = square((*wi).dot(wm.into()) + (*wo).dot(wm.into()) / etap);
let dwm_dwi = (*wi).dot(wm.into()).abs() / denom;
self.mf_distrib.pdf(*wo, wm.into()) * dwm_dwi * pr / (pr + pt)
let denom = square(wi.dot(wm.into()) + wo.dot(wm.into()) / etap);
let dwm_dwi = wi.dot(wm.into()).abs() / denom;
self.mf_distrib.pdf(wo, wm.into()) * dwm_dwi * pr / (pr + pt)
}
}
fn sample_f(&self, wo: &Vector3f, uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
fn sample_f(&self, wo: Vector3f, uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
if self.eta == 1. || self.mf_distrib.effectively_smooth() {
let r = fr_dielectric(cos_theta(*wo), self.eta);
let r = fr_dielectric(cos_theta(wo), self.eta);
let t = 1. - r;
let mut pr = r;
let mut pt = t;
@ -988,7 +952,7 @@ impl BxDFTrait for DielectricBxDF {
Some(bsdf)
} else {
// Compute ray direction for specular transmission
if let Some((wi, etap)) = refract(*wo, Normal3f::new(0., 0., 1.), self.eta) {
if let Some((wi, etap)) = refract(wo, Normal3f::new(0., 0., 1.), self.eta) {
let mut ft = SampledSpectrum::new(t / abs_cos_theta(wi));
if f_args.mode == TransportMode::Radiance {
ft /= square(etap);
@ -1008,8 +972,8 @@ impl BxDFTrait for DielectricBxDF {
}
} else {
// Sample rough dielectric BSDF
let wm = self.mf_distrib.sample_wm(*wo, u);
let r = fr_dielectric((*wo).dot(wm), self.eta);
let wm = self.mf_distrib.sample_wm(wo, u);
let r = fr_dielectric(wo.dot(wm), self.eta);
let t = 1. - r;
let mut pr = r;
let mut pt = t;
@ -1029,15 +993,15 @@ impl BxDFTrait for DielectricBxDF {
let pdf: Float;
if uc < pr / (pr + pt) {
// Sample reflection at rough dielectric interface
let wi = reflect(*wo, wm.into());
if !same_hemisphere(*wo, wi) {
let wi = reflect(wo, wm.into());
if !same_hemisphere(wo, wi) {
return None;
}
pdf = self.mf_distrib.pdf(*wo, wm) / (4. * (*wo).dot(wm).abs()) * pr / (pr + pt);
pdf = self.mf_distrib.pdf(wo, wm) / (4. * wo.dot(wm).abs()) * pr / (pr + pt);
let f = SampledSpectrum::new(
self.mf_distrib.d(wm) * self.mf_distrib.g(*wo, wi) * r
/ (4. * cos_theta(wi) * cos_theta(*wo)),
self.mf_distrib.d(wm) * self.mf_distrib.g(wo, wi) * r
/ (4. * cos_theta(wi) * cos_theta(wo)),
);
let bsdf = BSDFSample {
f,
@ -1049,18 +1013,18 @@ impl BxDFTrait for DielectricBxDF {
Some(bsdf)
} else {
// Sample transmission at rough dielectric interface
if let Some((wi, etap)) = refract(*wo, wm.into(), self.eta) {
if same_hemisphere(*wo, wi) || wi.z() == 0. {
if let Some((wi, etap)) = refract(wo, wm.into(), self.eta) {
if same_hemisphere(wo, wi) || wi.z() == 0. {
None
} else {
let denom = square(wi.dot(wm) + wo.dot(wm) / etap);
let dwm_mi = wi.dot(wm).abs() / denom;
pdf = self.mf_distrib.pdf(*wo, wm) * dwm_mi * pt / (pr + pt);
pdf = self.mf_distrib.pdf(wo, wm) * dwm_mi * pt / (pr + pt);
let mut ft = SampledSpectrum::new(
t * self.mf_distrib.d(wm)
* self.mf_distrib.g(*wo, wi)
* (wi.dot(wm) * (*wo).dot(wm)).abs()
/ (cos_theta(wi) * cos_theta(*wo) * denom),
* self.mf_distrib.g(wo, wi)
* (wi.dot(wm) * wo.dot(wm)).abs()
/ (cos_theta(wi) * cos_theta(wo) * denom),
);
if f_args.mode == TransportMode::Radiance {
ft /= square(etap);
@ -1096,16 +1060,16 @@ impl BxDFTrait for ThinDielectricBxDF {
BxDFFlags::REFLECTION | BxDFFlags::TRANSMISSION | BxDFFlags::SPECULAR
}
fn f(&self, _wo: &Vector3f, _wi: &Vector3f, _mode: TransportMode) -> SampledSpectrum {
fn f(&self, _wo: Vector3f, _wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
SampledSpectrum::new(0.)
}
fn pdf(&self, _wo: &Vector3f, _wi: &Vector3f, _f_args: FArgs) -> Float {
fn pdf(&self, _wo: Vector3f, _wi: Vector3f, _f_args: FArgs) -> Float {
0.
}
fn sample_f(&self, wo: &Vector3f, uc: Float, _u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let mut r = fr_dielectric(abs_cos_theta(*wo), self.eta);
fn sample_f(&self, wo: Vector3f, uc: Float, _u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let mut r = fr_dielectric(abs_cos_theta(wo), self.eta);
let mut t = 1. - r;
if r < 1. {
r += square(t) * r / (1. - square(r));
@ -1140,7 +1104,7 @@ impl BxDFTrait for ThinDielectricBxDF {
Some(bsdf)
} else {
// Perfect specular transmission
let wi = -(*wo);
let wi = -wo;
let f = SampledSpectrum::new(t / abs_cos_theta(wi));
let bsdf = BSDFSample {
f,
@ -1165,13 +1129,13 @@ impl BxDFTrait for MeasuredBxDF {
fn flags(&self) -> BxDFFlags {
BxDFFlags::REFLECTION | BxDFFlags::GLOSSY
}
fn f(&self, wo: &Vector3f, wi: &Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(*wo, *wi) {
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::new(0.);
}
let mut wo_curr = *wo;
let mut wi_curr = *wi;
let mut wo_curr = wo;
let mut wi_curr = wi;
if wo.z() < 0. {
wo_curr = -wo_curr;
wi_curr = -wi_curr;
@ -1211,21 +1175,21 @@ impl BxDFTrait for MeasuredBxDF {
});
fr * self.brdf.ndf.evaluate(u_wm, [])
/ (4. * self.brdf.sigma.evaluate(u_wo, []) * cos_theta(*wi))
/ (4. * self.brdf.sigma.evaluate(u_wo, []) * cos_theta(wi))
}
fn pdf(&self, wo: &Vector3f, wi: &Vector3f, f_args: FArgs) -> Float {
fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return 0.;
}
if !same_hemisphere(*wo, *wi) {
if !same_hemisphere(wo, wi) {
return 0.;
}
let mut wo_curr = *wo;
let mut wi_curr = *wi;
let mut wo_curr = wo;
let mut wi_curr = wi;
if wo.z() < 0. {
wo_curr = -wo_curr;
wi_curr = -wi_curr;
@ -1259,11 +1223,11 @@ impl BxDFTrait for MeasuredBxDF {
let pdf = self.brdf.luminance.evaluate(sample, [phi_o, theta_o]);
let sin_theta_m = (square(wm.x()) + square(wm.y())).sqrt();
let jacobian = 4. * wm.dot(*wo) * f32::max(2. * square(PI) * u_wm.x() * sin_theta_m, 1e-6);
let jacobian = 4. * wm.dot(wo) * f32::max(2. * square(PI) * u_wm.x() * sin_theta_m, 1e-6);
vndf_pdf * pdf / jacobian
}
fn sample_f(&self, wo: &Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option<BSDFSample> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
@ -1271,7 +1235,7 @@ impl BxDFTrait for MeasuredBxDF {
}
let mut flip_w = false;
let mut wo_curr = *wo;
let mut wo_curr = wo;
if wo.z() <= 0. {
wo_curr = -wo_curr;
flip_w = true;
@ -1344,7 +1308,7 @@ impl BxDFTrait for HairBxDF {
BxDFFlags::GLOSSY_REFLECTION
}
fn f(&self, wo: &Vector3f, wi: &Vector3f, _mode: TransportMode) -> SampledSpectrum {
fn f(&self, wo: Vector3f, wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
// Compute hair coordinate system terms related to wo
let sin_theta_o = wo.x();
let cos_theta_o = safe_sqrt(1. - square(sin_theta_o));
@ -1392,15 +1356,15 @@ impl BxDFTrait for HairBxDF {
) * ap
* Self::np(phi, p as i32, self.s, gamma_o, gamma_t);
}
if abs_cos_theta(*wi) > 0. {
f_sum /= abs_cos_theta(*wi);
if abs_cos_theta(wi) > 0. {
f_sum /= abs_cos_theta(wi);
}
f_sum
}
fn sample_f(
&self,
wo: &Vector3f,
wo: Vector3f,
mut uc: Float,
u: Point2f,
f_args: FArgs,
@ -1489,7 +1453,7 @@ impl BxDFTrait for HairBxDF {
* INV_2_PI;
let bsd = BSDFSample {
f: self.f(wo, &wi, f_args.mode),
f: self.f(wo, wi, f_args.mode),
wi,
pdf,
flags: self.flags(),
@ -1499,7 +1463,7 @@ impl BxDFTrait for HairBxDF {
Some(bsd)
}
fn pdf(&self, wo: &Vector3f, wi: &Vector3f, _f_args: FArgs) -> Float {
fn pdf(&self, wo: Vector3f, wi: Vector3f, _f_args: FArgs) -> Float {
let sin_theta_o = wo.x();
let cos_theta_o = safe_sqrt(1. - square(sin_theta_o));
let phi_o = wo.z().atan2(wo.y());

View file

@ -239,7 +239,7 @@ impl PixelSensor {
}
pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let l_norm = l.safe_div(lambda.pdf());
let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf());
self.imaging_ratio
* RGB::new(
(self.r_bar.sample(lambda) * l_norm).average(),

View file

@ -1,6 +1,6 @@
use crate::camera::{Camera, CameraTrait};
use crate::core::bxdf::{BSDF, BxDFFlags};
use crate::core::material::MaterialTrait;
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::{Float, clamp_t};
use crate::geometry::{
@ -93,8 +93,8 @@ pub struct ShadingGeometry {
pub dndv: Normal3f,
}
#[derive(Default, Debug, Clone)]
pub struct SurfaceInteraction {
#[derive(Default, Debug)]
pub struct SurfaceInteraction<'a> {
pub common: InteractionData,
pub uv: Point2f,
pub dpdu: Vector3f,
@ -105,7 +105,7 @@ pub struct SurfaceInteraction {
pub medium_interface: Option<Arc<MediumInterface>>,
pub face_index: usize,
pub area_light: Option<Arc<Light>>,
pub material: Option<Arc<dyn MaterialTrait>>,
pub material: Option<Arc<Material>>,
pub dpdx: Vector3f,
pub dpdy: Vector3f,
pub dudx: Float,
@ -113,10 +113,10 @@ pub struct SurfaceInteraction {
pub dudy: Float,
pub dvdy: Float,
pub shape: Arc<Shape>,
pub bsdf: Option<BSDF>,
// pub bsdf: Option<BSDF<'a>>,
}
impl SurfaceInteraction {
impl<'a> SurfaceInteraction<'a> {
pub fn compute_differentials(&mut self, r: &Ray, camera: Camera, samples_per_pixel: i32) {
let computed = if let Some(diff) = &r.differential {
let dot_rx = self.common.n.dot(diff.rx_direction.into());
@ -506,7 +506,7 @@ impl SurfaceInteraction {
pub fn set_intersection_properties(
&mut self,
mtl: Arc<dyn MaterialTrait>,
mtl: Arc<Material>,
area: Arc<Light>,
prim_medium_interface: Option<Arc<MediumInterface>>,
ray_medium: Arc<Medium>,

View file

@ -1,27 +1,56 @@
#[derive(Clone, Debug)]
pub struct CoatedDiffuseMaterial;
#[derive(Clone, Debug)]
pub struct CoatedConductorMaterial;
#[derive(Clone, Debug)]
pub struct ConductorMaterial;
#[derive(Clone, Debug)]
pub struct DielectricMaterial;
#[derive(Clone, Debug)]
pub struct DiffuseMaterial;
#[derive(Clone, Debug)]
pub struct DiffuseTransmissionMaterial;
#[derive(Clone, Debug)]
pub struct HairMaterial;
#[derive(Clone, Debug)]
pub struct MeasuredMaterial;
#[derive(Clone, Debug)]
pub struct SubsurfaceMaterial;
#[derive(Clone, Debug)]
pub struct ThinDielectricMaterial;
#[derive(Clone, Debug)]
pub struct MixMaterial;
use bumpalo::Bump;
use enum_dispatch::enum_dispatch;
use std::ops::Deref;
use std::sync::Arc;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::{BSDF, DiffuseBxDF};
use crate::core::texture::{FloatTexture, SpectrumTexture, TextureEvalContext, TextureEvaluator};
use crate::geometry::{Normal3f, Vector3f};
use crate::image::Image;
use crate::spectra::SampledWavelengths;
#[derive(Clone, Debug)]
pub struct MaterialEvalContext {
texture: TextureEvalContext,
wo: Vector3f,
ns: Normal3f,
dpdus: Vector3f,
}
impl Deref for MaterialEvalContext {
type Target = TextureEvalContext;
fn deref(&self) -> &Self::Target {
&self.texture
}
}
#[enum_dispatch]
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
scratch: &'a Bump,
) -> BSDF<'a>;
fn get_bssrdf<'a, T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSSRDF<'a>;
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
fn get_normal_map(&self) -> Image;
fn get_displacement(&self) -> FloatTexture;
fn has_surface_scattering(&self) -> bool;
}
#[derive(Clone, Debug)]
#[enum_dispatch(MaterialTrait)]
pub enum Material {
CoatedDiffuse(CoatedDiffuseMaterial),
CoatedConductor(CoatedConductorMaterial),
@ -36,6 +65,381 @@ pub enum Material {
Mix(MixMaterial),
}
impl Material {}
#[derive(Clone, Debug)]
pub struct CoatedDiffuseMaterial;
impl MaterialTrait for CoatedDiffuseMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct CoatedConductorMaterial;
impl MaterialTrait for CoatedConductorMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct ConductorMaterial;
impl MaterialTrait for ConductorMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct DielectricMaterial;
impl MaterialTrait for DielectricMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct DiffuseMaterial {
normal_map: Option<Arc<Image>>,
displacement: FloatTexture,
reflectance: SpectrumTexture,
}
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {}
impl MaterialTrait for DiffuseMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
scratch: &'a Bump,
) -> BSDF<'a> {
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
let bxdf = scratch.alloc(DiffuseBxDF::new(r));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct DiffuseTransmissionMaterial;
impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct HairMaterial;
impl MaterialTrait for HairMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct MeasuredMaterial;
impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct SubsurfaceMaterial;
impl MaterialTrait for SubsurfaceMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct ThinDielectricMaterial;
impl MaterialTrait for ThinDielectricMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct MixMaterial;
impl MaterialTrait for MixMaterial {
fn get_bsdf<'a, T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
_scratch: &'a Bump,
) -> BSDF<'a> {
todo!()
}
fn get_bssrdf<'a, T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSSRDF<'a> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Image {
todo!()
}
fn get_displacement(&self) -> FloatTexture {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}

View file

@ -1,4 +1,5 @@
pub mod aggregates;
pub mod bssrdf;
pub mod bxdf;
pub mod cie;
pub mod film;

View file

@ -1,6 +1,6 @@
use crate::core::aggregates::LinearBVHNode;
use crate::core::interaction::{Interaction, SurfaceInteraction};
use crate::core::material::MaterialTrait;
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::Float;
use crate::core::texture::{FloatTextureTrait, TextureEvalContext};
@ -21,7 +21,7 @@ pub trait PrimitiveTrait: Send + Sync + std::fmt::Debug {
#[derive(Debug, Clone)]
pub struct GeometricPrimitive {
shape: Arc<Shape>,
material: Arc<dyn MaterialTrait>,
material: Arc<Material>,
area_light: Arc<Light>,
medium_interface: Arc<MediumInterface>,
alpha: Option<Arc<dyn FloatTextureTrait>>,
@ -81,7 +81,7 @@ impl PrimitiveTrait for GeometricPrimitive {
#[derive(Debug, Clone)]
pub struct SimplePrimitiv {
shape: Arc<Shape>,
material: Arc<dyn MaterialTrait>,
material: Arc<Material>,
}
#[derive(Debug, Clone)]

View file

@ -1,12 +1,14 @@
use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float;
use crate::core::pbrt::{INV_2_PI, INV_PI, PI};
use crate::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike};
use crate::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::geometry::{spherical_phi, spherical_theta};
use crate::image::WrapMode;
use crate::spectra::{SampledSpectrum, Spectrum};
use crate::spectra::{
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, Spectrum,
};
use crate::spectra::{SampledWavelengths, SpectrumTrait};
use crate::utils::color::ColorEncoding;
use crate::utils::color::{ColorEncoding, RGB};
use crate::utils::math::square;
use crate::utils::mipmap::MIPMap;
use crate::utils::mipmap::{MIPMapFilterOptions, MIPMapSample};
@ -16,6 +18,7 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use enum_dispatch::enum_dispatch;
use std::path::Path;
struct TexCoord2D {
st: Point2f,
@ -27,10 +30,11 @@ struct TexCoord2D {
#[enum_dispatch]
trait TextureMapping2DTrait {
fn map(&self, ctx: TextureEvalContext) -> TexCoord2D;
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D;
}
#[enum_dispatch(TextureMapping2DTrait)]
#[derive(Clone, Debug)]
pub enum TextureMapping2D {
UV(UVMapping),
Spherical(SphericalMapping),
@ -38,6 +42,7 @@ pub enum TextureMapping2D {
Planar(PlanarMapping),
}
#[derive(Clone, Debug)]
pub struct UVMapping {
su: Float,
sv: Float,
@ -57,7 +62,7 @@ impl Default for UVMapping {
}
impl TextureMapping2DTrait for UVMapping {
fn map(&self, ctx: TextureEvalContext) -> TexCoord2D {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let dsdx = self.su * ctx.dudx;
let dsdy = self.su * ctx.dudy;
let dtdx = self.sv * ctx.dvdx;
@ -72,6 +77,8 @@ impl TextureMapping2DTrait for UVMapping {
}
}
}
#[derive(Clone, Debug)]
pub struct SphericalMapping {
texture_from_render: Transform<Float>,
}
@ -85,7 +92,7 @@ impl SphericalMapping {
}
impl TextureMapping2DTrait for SphericalMapping {
fn map(&self, ctx: TextureEvalContext) -> TexCoord2D {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let pt = self.texture_from_render.apply_to_point(ctx.p);
let x2y2 = square(pt.x()) + square(pt.y());
let sqrtx2y2 = x2y2.sqrt();
@ -114,6 +121,7 @@ impl TextureMapping2DTrait for SphericalMapping {
}
}
#[derive(Clone, Debug)]
pub struct CylindricalMapping {
texture_from_render: Transform<Float>,
}
@ -127,7 +135,7 @@ impl CylindricalMapping {
}
impl TextureMapping2DTrait for CylindricalMapping {
fn map(&self, ctx: TextureEvalContext) -> TexCoord2D {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let pt = self.texture_from_render.apply_to_point(ctx.p);
let x2y2 = square(pt.x()) + square(pt.y());
let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2);
@ -149,6 +157,7 @@ impl TextureMapping2DTrait for CylindricalMapping {
}
}
#[derive(Clone, Debug)]
pub struct PlanarMapping {
texture_from_render: Transform<Float>,
vs: Vector3f,
@ -176,7 +185,7 @@ impl PlanarMapping {
}
impl TextureMapping2DTrait for PlanarMapping {
fn map(&self, ctx: TextureEvalContext) -> TexCoord2D {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let vec: Vector3f = self.texture_from_render.apply_to_point(ctx.p).into();
let dpdx = self.texture_from_render.apply_to_vector(ctx.dpdx);
let dpdy = self.texture_from_render.apply_to_vector(ctx.dpdy);
@ -234,6 +243,7 @@ impl TextureMapping3DTrait for PointTransformMapping {
}
}
#[derive(Clone, Debug)]
pub struct TextureEvalContext {
p: Point3f,
dpdx: Vector3f,
@ -493,10 +503,68 @@ impl SpectrumTextureTrait for SpectrumCheckerboardTexture {
}
#[derive(Clone, Debug)]
pub struct SpectrumImageTexture;
pub enum SpectrumType {
Illuminant,
Albedo,
Unbounded,
}
#[derive(Clone, Debug)]
pub struct SpectrumImageTexture {
base: ImageTextureBase,
spectrum_type: SpectrumType,
}
impl SpectrumImageTexture {
#[allow(clippy::too_many_arguments)]
pub fn new(
mapping: TextureMapping2D,
filename: String,
filter_options: MIPMapFilterOptions,
wrap_mode: WrapMode,
scale: Float,
invert: bool,
encoding: ColorEncoding,
spectrum_type: SpectrumType,
) -> Self {
let base = ImageTextureBase::new(
mapping,
filename,
filter_options,
wrap_mode,
scale,
invert,
encoding,
);
Self {
base,
spectrum_type,
}
}
}
impl SpectrumTextureTrait for SpectrumImageTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut c = self.base.mapping.map(ctx);
c.st[1] = 1. - c.st[1];
let dst0 = Vector2f::new(c.dsdx, c.dtdx);
let dst1 = Vector2f::new(c.dsdy, c.dtdy);
let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1);
let rgb = RGB::clamp_zero(rgb_unclamp);
if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
match self.spectrum_type {
SpectrumType::Unbounded => {
return RGBUnboundedSpectrum::new(&cs, rgb).sample(lambda);
}
SpectrumType::Albedo => {
return RGBAlbedoSpectrum::new(&cs, rgb).sample(lambda);
}
_ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda),
}
}
assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]);
SampledSpectrum::new(rgb[0])
}
}
@ -595,4 +663,116 @@ struct TexInfo {
static TEXTURE_CACHE: OnceLock<Mutex<HashMap<TexInfo, Arc<MIPMap>>>> = OnceLock::new();
pub struct ImageTextureBase {}
fn get_texture_cache() -> &'static Mutex<HashMap<TexInfo, Arc<MIPMap>>> {
TEXTURE_CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
#[derive(Clone, Debug)]
pub struct ImageTextureBase {
mapping: TextureMapping2D,
filename: String,
scale: Float,
invert: bool,
mipmap: Arc<MIPMap>,
}
impl ImageTextureBase {
pub fn new(
mapping: TextureMapping2D,
filename: String,
filter_options: MIPMapFilterOptions,
wrap_mode: WrapMode,
scale: Float,
invert: bool,
encoding: ColorEncoding,
) -> Self {
let tex_info = TexInfo {
filename: filename.clone(),
filter_options,
wrap_mode,
encoding,
};
let cache_mutex = get_texture_cache();
{
let cache = cache_mutex.lock().unwrap();
if let Some(mipmap) = cache.get(&tex_info) {
return Self {
mapping,
filename,
scale,
invert,
mipmap: mipmap.clone(),
};
}
}
let path = Path::new(&filename);
let mipmap_raw = MIPMap::create_from_file(&path, filter_options, wrap_mode, encoding)
.expect("Failed to create MIPMap from file");
let mipmap_arc = Arc::new(mipmap_raw);
{
let mut cache = cache_mutex.lock().unwrap();
let stored_mipmap = cache.entry(tex_info).or_insert(mipmap_arc);
Self {
mapping,
filename,
scale,
invert,
mipmap: stored_mipmap.clone(),
}
}
}
pub fn clear_cache() {
let mut cache = get_texture_cache().lock().unwrap();
cache.clear();
}
pub fn multiply_scale(&mut self, s: Float) {
self.scale *= s;
}
}
pub trait TextureEvaluator: Send + Sync {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float;
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum;
fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool;
}
#[derive(Copy, Clone, Default)]
pub struct UniversalTextureEvaluator;
impl TextureEvaluator for UniversalTextureEvaluator {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float {
tex.evaluate(ctx)
}
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
tex.evaluate(ctx, lambda)
}
fn can_evaluate(
&self,
_float_textures: &[&FloatTexture],
_spectrum_textures: &[&SpectrumTexture],
) -> bool {
true
}
}

View file

@ -1,5 +1,7 @@
use crate::core::pbrt::Float;
use crate::image::{Image, ImageMetadata, PixelData, PixelFormat, Point2i, WrapMode};
use crate::image::{
Image, ImageAndMetadata, ImageMetadata, PixelData, PixelFormat, Point2i, WrapMode,
};
use crate::utils::color::{ColorEncoding, LINEAR, SRGB};
use crate::utils::error::ImageError;
use anyhow::{Context, Result, bail};
@ -11,7 +13,7 @@ use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path;
impl Image {
pub fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<Self> {
pub fn read(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata> {
let ext = path
.extension()
.and_then(|s| s.to_str())
@ -179,7 +181,7 @@ impl Image {
}
}
fn read_generic(path: &Path, encoding: Option<ColorEncoding>) -> Result<Image> {
fn read_generic(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAndMetadata> {
// 1. Load via 'image' crate
let dyn_img = ImageReader::open(path)
.with_context(|| format!("Failed to open image: {:?}", path))?
@ -189,50 +191,51 @@ fn read_generic(path: &Path, encoding: Option<ColorEncoding>) -> Result<Image> {
let h = dyn_img.height() as i32;
let res = Point2i::new(w, h);
// 2. Convert to PBRT Image
// Check if it was loaded as high precision (HDR/EXR via image crate) or standard
match dyn_img {
DynamicImage::ImageRgb32F(buf) => Ok(Image {
let image = match dyn_img {
DynamicImage::ImageRgb32F(buf) => Image {
format: PixelFormat::F32,
resolution: res,
channel_names: vec!["R".into(), "G".into(), "B".into()],
encoding: LINEAR,
pixels: PixelData::F32(buf.into_raw()),
}),
DynamicImage::ImageRgba32F(buf) => Ok(Image {
},
DynamicImage::ImageRgba32F(buf) => Image {
format: PixelFormat::F32,
resolution: res,
channel_names: vec!["R".into(), "G".into(), "B".into(), "A".into()],
encoding: LINEAR,
pixels: PixelData::F32(buf.into_raw()),
}),
},
_ => {
// Default to RGB8 for everything else (PNG, JPG)
// Note: PBRT handles alpha, so we check if the source had alpha
if dyn_img.color().has_alpha() {
let buf = dyn_img.to_rgba8();
Ok(Image {
Image {
format: PixelFormat::U8,
resolution: res,
channel_names: vec!["R".into(), "G".into(), "B".into(), "A".into()],
encoding: encoding.unwrap_or(SRGB),
pixels: PixelData::U8(buf.into_raw()),
})
}
} else {
let buf = dyn_img.to_rgb8();
Ok(Image {
Image {
format: PixelFormat::U8,
resolution: res,
channel_names: vec!["R".into(), "G".into(), "B".into()],
encoding: encoding.unwrap_or(SRGB),
pixels: PixelData::U8(buf.into_raw()),
})
}
}
}
}
};
let metadata = ImageMetadata::default();
Ok(ImageAndMetadata { image, metadata })
}
fn read_exr(path: &Path) -> Result<Image> {
fn read_exr(path: &Path) -> Result<ImageAndMetadata> {
let image = read_first_rgba_layer_from_file(
path,
|resolution, _| {
@ -254,16 +257,19 @@ fn read_exr(path: &Path) -> Result<Image> {
let w = image.layer_data.size.width() as i32;
let h = image.layer_data.size.height() as i32;
Ok(Image {
let image = Image {
format: PixelFormat::F32,
resolution: Point2i::new(w, h),
channel_names: vec!["R".into(), "G".into(), "B".into(), "A".into()],
encoding: LINEAR,
pixels: PixelData::F32(image.layer_data.channel_data.pixels),
})
};
let metadata = ImageMetadata::default();
Ok(ImageAndMetadata { image, metadata })
}
fn read_pfm(path: &Path) -> Result<Image> {
fn read_pfm(path: &Path) -> Result<ImageAndMetadata> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
@ -335,11 +341,14 @@ fn read_pfm(path: &Path) -> Result<Image> {
vec!["R".into(), "G".into(), "B".into()]
};
Ok(Image {
let image = Image {
format: PixelFormat::F32,
resolution: Point2i::new(w, h),
channel_names: names,
encoding: LINEAR,
pixels: PixelData::F32(pixels),
})
};
let metadata = ImageMetadata::default();
Ok(ImageAndMetadata { image, metadata })
}

View file

@ -54,7 +54,7 @@ pub struct ImageMetadata {
pub full_resolution: Option<Point2i>,
pub samples_per_pixel: Option<i32>,
pub mse: Option<Float>,
pub color_space: Option<&'static RGBColorSpace>,
pub colorspace: Option<RGBColorSpace>,
pub strings: HashMap<String, String>,
pub string_vectors: HashMap<String, Vec<String>>,
}

View file

@ -6,7 +6,7 @@ pub mod sphere;
pub mod triangle;
use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction};
use crate::core::material::MaterialTrait;
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::{Float, PI};
use crate::geometry::{
@ -177,7 +177,7 @@ impl ShapeIntersection {
pub fn set_intersection_properties(
&mut self,
mtl: Arc<dyn MaterialTrait>,
mtl: Arc<Material>,
area: Arc<Light>,
prim_medium_interface: Option<Arc<MediumInterface>>,
ray_medium: Option<Arc<Medium>>,

View file

@ -19,7 +19,7 @@ impl RGBAlbedoSpectrum {
}
}
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
for i in 0..N_SPECTRUM_SAMPLES {
s[i] = self.rsp.evaluate(lambda[i]);
@ -78,7 +78,7 @@ pub struct RGBIlluminantSpectrum {
}
impl RGBIlluminantSpectrum {
pub fn new(cs: RGBColorSpace, rgb: RGB) -> Self {
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
let illuminant = &cs.illuminant;
let densely_sampled =
DenselySampledSpectrum::from_spectrum(illuminant, LAMBDA_MIN, LAMBDA_MAX);
@ -95,6 +95,13 @@ impl RGBIlluminantSpectrum {
illuminant: Some(Arc::new(densely_sampled)),
}
}
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
if self.illuminant.is_none() {
return SampledSpectrum::new(0.);
}
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
}
}
impl SpectrumTrait for RGBIlluminantSpectrum {
@ -153,11 +160,7 @@ impl RGBUnboundedSpectrum {
}
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
for i in 0..N_SPECTRUM_SAMPLES {
s[i] = self.scale * self.rsp.evaluate(lambda[i]);
}
s
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
}
}

View file

@ -68,18 +68,6 @@ impl SampledSpectrum {
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
}
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
let mut r = SampledSpectrum::new(0.0);
for i in 0..N_SPECTRUM_SAMPLES {
r.values[i] = if rhs[i] != 0.0 {
self.values[i] / rhs.values[i]
} else {
0.0
}
}
r
}
pub fn exp(&self) -> SampledSpectrum {
let values = self.values.map(|v| v.exp());
let ret = Self { values };
@ -99,6 +87,16 @@ impl SampledSpectrum {
}
result
}
pub fn clamp_zero(s: &SampledSpectrum) -> Self {
let ret = SampledSpectrum::from_fn(|i| s[i].max(0.));
assert!(!ret.has_nans());
ret
}
pub fn safe_div(a: &SampledSpectrum, b: &SampledSpectrum) -> SampledSpectrum {
SampledSpectrum::from_fn(|i| if b[i] != 0. { a[i] / b[i] } else { 0. })
}
}
impl<'a> IntoIterator for &'a SampledSpectrum {

View file

@ -286,6 +286,10 @@ impl RGB {
pub fn max(&self) -> Float {
self.r.max(self.g).max(self.b)
}
pub fn clamp_zero(rgb: Self) -> Self {
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.b.max(0.))
}
}
impl Index<usize> for RGB {

View file

@ -10,11 +10,6 @@ use std::cmp::{Eq, PartialEq};
use std::error::Error;
use std::sync::Arc;
pub enum ColorEncoding {
Linear,
SRGB,
}
#[derive(Debug, Clone)]
pub struct RGBColorSpace {
pub r: Point2f,

View file

@ -243,6 +243,47 @@ pub fn linear_least_squares<const R: usize, const N: usize>(
Ok((at_ai * atb).transpose())
}
pub fn newton_bisection<P>(mut x0: Float, mut x1: Float, mut f: P) -> Float
where
P: FnMut(Float) -> (Float, Float),
{
assert!(x0 < x1);
let f_eps = 1e-6;
let x_eps = 1e-6;
let (fx0, _) = f(x0);
let (fx1, _) = f(x1);
if fx0.abs() < f_eps {
return x0;
}
if fx1.abs() < f_eps {
return x1;
}
let start_is_negative = fx0 < 0.0;
let mut x_mid = x0 + (x1 - x0) * -fx0 / (fx1 - fx0);
loop {
if !(x0 < x_mid && x_mid < x1) {
x_mid = (x0 + x1) / 2.0;
}
let (fx_mid, dfx_mid) = f(x_mid);
assert!(!fx_mid.is_nan());
if start_is_negative == (fx_mid < 0.0) {
x0 = x_mid;
} else {
x1 = x_mid;
}
if (x1 - x0) < x_eps || fx_mid.abs() < f_eps {
return x_mid;
}
x_mid -= fx_mid / dfx_mid;
}
}
pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f {
if uv[0] < 0. {
uv[0] = -uv[0];
@ -261,6 +302,66 @@ pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f {
*uv
}
pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(usize, [Float; 4])> {
if nodes.len() < 4 {
return None;
}
// Return None if x is out of bounds
if x < *nodes.first()? || x > *nodes.last()? {
return None;
}
// Search for interval idx containing x
// partition_point returns the first index where predicate is false (!= <= x means > x)
// Equivalent to upper_bound. We subtract 1 to get the interval index.
let idx = nodes.partition_point(|&n| n <= x).saturating_sub(1);
// Safety clamp (though bounds check above handles most cases)
let idx = idx.min(nodes.len() - 2);
let offset = idx.saturating_sub(1); // The C++ code uses idx - 1 for the offset
let x0 = nodes[idx];
let x1 = nodes[idx + 1];
// Compute t parameter and powers
let t = (x - x0) / (x1 - x0);
let t2 = t * t;
let t3 = t2 * t;
let mut weights = [0.0; 4];
// Compute initial node weights w1 and w2
weights[1] = 2.0 * t3 - 3.0 * t2 + 1.0;
weights[2] = -2.0 * t3 + 3.0 * t2;
// Compute first node weight w0
if idx > 0 {
let w0 = (t3 - 2.0 * t2 + t) * (x1 - x0) / (x1 - nodes[idx - 1]);
weights[0] = -w0;
weights[2] += w0;
} else {
let w0 = t3 - 2.0 * t2 + t;
weights[0] = 0.0;
weights[1] -= w0;
weights[2] += w0;
}
// Compute last node weight w3
if idx + 2 < nodes.len() {
let w3 = (t3 - t2) * (x1 - x0) / (nodes[idx + 2] - x0);
weights[1] -= w3;
weights[3] = w3;
} else {
let w3 = t3 - t2;
weights[1] -= w3;
weights[2] += w3;
weights[3] = 0.0;
}
Some((offset, weights))
}
pub fn equal_area_square_to_sphere(p: Point2f) -> Vector3f {
assert!(p.x() >= 0. && p.x() <= 1. && p.y() >= 0. && p.y() <= 1.);

View file

@ -1,8 +1,10 @@
use crate::core::pbrt::{Float, lerp};
use crate::geometry::{Lerp, Point2f, Point2i, Vector2f, VectorLike};
use crate::image::{Image, PixelData, PixelFormat, WrapMode, WrapMode2D};
use crate::utils::color::RGB;
use crate::image::{Image, ImageAndMetadata, PixelData, PixelFormat, WrapMode, WrapMode2D};
use crate::utils::color::{ColorEncoding, RGB};
use crate::utils::colorspace::RGBColorSpace;
use crate::utils::math::{safe_sqrt, square};
use std::path::Path;
use std::hash::{Hash, Hasher};
use std::ops::{Add, Mul, Sub};
@ -117,7 +119,7 @@ impl MIPMapSample for RGB {
#[derive(Debug)]
pub struct MIPMap {
pyramid: Vec<Image>,
color_space: Arc<RGBColorSpace>,
color_space: Option<RGBColorSpace>,
wrap_mode: WrapMode,
options: MIPMapFilterOptions,
}
@ -125,7 +127,7 @@ pub struct MIPMap {
impl MIPMap {
pub fn new(
image: Image,
color_space: Arc<RGBColorSpace>,
color_space: Option<RGBColorSpace>,
wrap_mode: WrapMode,
options: MIPMapFilterOptions,
) -> Self {
@ -146,7 +148,7 @@ impl MIPMap {
self.pyramid.len()
}
pub fn get_rgb_colorspace(&self) -> Arc<RGBColorSpace> {
pub fn get_rgb_colorspace(&self) -> Option<RGBColorSpace> {
self.color_space.clone()
}
@ -178,7 +180,6 @@ impl MIPMap {
let n_levels = self.levels() as Float;
let level = n_levels - 1.0 + width.max(1e-8).log2();
// Handle very wide filter (return the 1x1 average pixel)
if level >= n_levels - 1.0 {
return self.texel(self.levels() - 1, Point2i::new(0, 0));
}
@ -187,7 +188,6 @@ impl MIPMap {
return match self.options.filter {
FilterFunction::Point => {
// Nearest Neighbor
let resolution = self.level_resolution(i_level);
let sti = Point2i::new(
(st.x() * resolution.x() as Float - 0.5).round() as i32,
@ -236,8 +236,17 @@ impl MIPMap {
lerp(lod - ilod as Float, v0, v1)
}
fn texel<T: MIPMapSample>(&self, _level: usize, _st: Point2i) -> T {
todo!()
fn texel<T: MIPMapSample>(&self, level: usize, st: Point2i) -> T {
if level >= self.levels() {
panic!("MIPMap level out of bounds");
}
let image = &self.pyramid[level];
let wrap_2d = WrapMode2D {
uv: [self.wrap_mode; 2],
};
T::sample_texel(image, st, wrap_2d)
}
fn bilerp<T: MIPMapSample>(&self, level: usize, st: Point2f) -> T {
@ -250,11 +259,214 @@ impl MIPMap {
fn ewa<T: MIPMapSample>(
&self,
_scale: usize,
_st: Point2f,
_dst0: Vector2f,
_dst1: Vector2f,
level: usize,
mut st: Point2f,
mut dst0: Vector2f,
mut dst1: Vector2f,
) -> T {
todo!()
if level > self.levels() {
return self.texel(self.levels() - 1, Point2i::new(0, 0));
}
let level_res = self.level_resolution(level);
st[0] = st[0] * level_res[0] as Float - 0.5;
st[1] = st[1] * level_res[1] as Float - 0.5;
dst0[0] *= level_res[0] as Float;
dst0[1] *= level_res[1] as Float;
dst1[0] *= level_res[0] as Float;
dst1[1] *= level_res[1] as Float;
let mut a = square(dst0[1]) + square(dst1[1]) + 1.;
let mut b = -2. * (dst0[0] + dst0[1] + dst1[1]);
let mut c = square(dst0[0]) + square(dst1[0]) + 1.;
let inv_f = 1. / (a * c - square(b) * 0.25);
a *= inv_f;
b *= inv_f;
c *= inv_f;
let det = -square(b) + 4. * a * c;
let inv_det = 1. / det;
let u_sqrt = safe_sqrt(det * c);
let v_sqrt = safe_sqrt(det * a);
let s0: i32 = (st[0] - 2. * inv_det * u_sqrt).ceil() as i32;
let s1: i32 = (st[0] + 2. * inv_det * u_sqrt).floor() as i32;
let t0: i32 = (st[1] - 2. * inv_det * v_sqrt).ceil() as i32;
let t1: i32 = (st[1] + 2. * inv_det * v_sqrt).floor() as i32;
let mut sum = T::zero();
let mut sum_wts = 0.;
for it in t0..=t1 {
let tt = it as Float - st[1];
for is in s0..=s1 {
let ss = is as Float - st[0];
// Compute squared radius and filter texel if inside ellipse
let r2 = a * square(ss) + b * ss * tt + c * square(tt);
if r2 < 1.0 {
// Map r2 to LUT index
let index = (r2 * MIP_FILTER_LUT_SIZE as Float)
.min((MIP_FILTER_LUT_SIZE - 1) as Float)
as usize;
let weight = MIP_FILTER_LUT[index];
// Accumulate
sum = sum + self.texel::<T>(level, Point2i::new(is, it)) * weight;
sum_wts += weight;
}
}
}
sum * (1. / sum_wts)
}
pub fn create_from_file(
filename: &Path,
options: MIPMapFilterOptions,
wrap_mode: WrapMode,
encoding: ColorEncoding,
) -> Result<MIPMap, ()> {
let image_and_metadata = Image::read(filename, Some(encoding)).unwrap();
let image = image_and_metadata.image;
// if image.n_channels() != 1 {
// let rgba_dsc = image.all_channels_desc();
// }
Ok(MIPMap::new(
image,
image_and_metadata.metadata.colorspace,
wrap_mode,
options,
))
}
}
static MIP_FILTER_LUT_SIZE: usize = 128;
static MIP_FILTER_LUT: [Float; MIP_FILTER_LUT_SIZE] = [
// MIPMap EWA Lookup Table Values
0.864664733,
0.849040031,
0.83365953,
0.818519294,
0.80361563,
0.788944781,
0.774503231,
0.760287285,
0.746293485,
0.732518315,
0.718958378,
0.705610275,
0.692470789,
0.679536581,
0.666804492,
0.654271305,
0.641933978,
0.629789352,
0.617834508,
0.606066525,
0.594482362,
0.583079159,
0.571854174,
0.560804546,
0.549927592,
0.539220572,
0.528680861,
0.518305838,
0.50809288,
0.498039544,
0.488143265,
0.478401601,
0.468812168,
0.45937258,
0.450080454,
0.440933526,
0.431929469,
0.423066139,
0.414341331,
0.405752778,
0.397298455,
0.388976216,
0.380784035,
0.372719884,
0.364781618,
0.356967449,
0.34927541,
0.341703475,
0.334249914,
0.32691282,
0.319690347,
0.312580705,
0.305582166,
0.298692942,
0.291911423,
0.285235822,
0.278664529,
0.272195935,
0.265828371,
0.259560347,
0.253390193,
0.247316495,
0.241337672,
0.235452279,
0.229658857,
0.223955944,
0.21834214,
0.212816045,
0.207376286,
0.202021524,
0.196750447,
0.191561714,
0.186454013,
0.181426153,
0.176476851,
0.171604887,
0.166809067,
0.162088141,
0.157441005,
0.152866468,
0.148363426,
0.143930718,
0.139567271,
0.135272011,
0.131043866,
0.126881793,
0.122784719,
0.11875169,
0.114781633,
0.11087364,
0.107026696,
0.103239879,
0.0995122194,
0.0958427936,
0.0922307223,
0.0886750817,
0.0851749927,
0.0817295909,
0.0783380121,
0.0749994367,
0.0717130303,
0.0684779733,
0.0652934611,
0.0621587038,
0.0590728968,
0.0560353249,
0.0530452281,
0.0501018465,
0.0472044498,
0.0443523228,
0.0415447652,
0.0387810767,
0.0360605568,
0.0333825648,
0.0307464004,
0.0281514227,
0.0255970061,
0.0230824798,
0.0206072628,
0.0181707144,
0.0157722086,
0.013411209,
0.0110870898,
0.0087992847,
0.0065472275,
0.00433036685,
0.0021481365,
0.,
];

View file

@ -1,15 +1,18 @@
use super::math::safe_sqrt;
use crate::check_rare;
use crate::core::pbrt::{
Float, INV_2_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, clamp_t, find_interval,
lerp,
Float, INV_2_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, clamp_t,
evaluate_polynomial, find_interval, lerp,
};
use crate::core::pbrt::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
use crate::geometry::{
Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike,
};
use crate::utils::containers::Array2D;
use crate::utils::math::{difference_of_products, logistic, square, sum_of_products};
use crate::utils::math::{
catmull_rom_weights, difference_of_products, logistic, newton_bisection, square,
sum_of_products,
};
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
@ -428,6 +431,151 @@ pub fn invert_spherical_triangle_sample(
Some(Point2f::new(clamp_t(u0, 0.0, 1.0), clamp_t(u1, 0.0, 1.0)))
}
pub fn sample_catmull_rom(
nodes: &[Float],
f: &[Float],
big_f: &[Float],
mut u: Float,
) -> (Float, Float, Float) {
assert_eq!(nodes.len(), f.len());
assert_eq!(f.len(), big_f.len());
u *= big_f.last().copied().unwrap_or(0.);
let i = find_interval(big_f.len(), |i| big_f[i] <= u);
let x0 = nodes[i];
let x1 = nodes[i + 1];
let f0 = f[i];
let f1 = f[i + 1];
let width = x1 - x0;
// Approximate derivatives using finite differences
let d0 = if i > 0 {
width * (f1 - f[i - 1]) / (x1 - nodes[i - 1])
} else {
f1 - f0
};
let d1 = if i + 2 < nodes.len() {
width * (f[i + 2] - f0) / (nodes[i + 2] - x0)
} else {
f1 - f0
};
u = (u - big_f[i]) / width;
let fhat_coeffs = [
f0,
d0,
-2.0 * d0 - d1 + 3.0 * (f1 - f0),
d0 + d1 + 2.0 * (f0 - f1),
];
let fhat_coeffs_ref = &fhat_coeffs;
let big_fhat_coeffs = [
0.0,
f0,
0.5 * d0,
(1.0 / 3.0) * (-2.0 * d0 - d1) + f1 - f0,
0.25 * (d0 + d1) + 0.5 * (f0 - f1),
];
let big_fhat_coeffs_ref = &big_fhat_coeffs;
let mut fhat = 0.;
let mut big_fhat = 0.;
let eval = |t: Float| -> (Float, Float) {
big_fhat = evaluate_polynomial(t, big_fhat_coeffs_ref).unwrap_or(0.);
fhat = evaluate_polynomial(t, fhat_coeffs_ref).unwrap_or(0.);
(big_fhat - u, fhat)
};
let t = newton_bisection(0., 1., eval);
(
x0 + width * t,
fhat,
fhat / big_f.last().copied().unwrap_or(1.0),
)
}
pub fn sample_catmull_rom_2d(
nodes1: &[Float],
nodes2: &[Float],
values: &[Float],
cdf: &[Float],
alpha: Float,
mut u: Float,
) -> (Float, Float, Float) {
let (offset, weights) = match catmull_rom_weights(nodes1, alpha) {
Some(res) => res,
None => return (0., 0., 0.),
};
let n2 = nodes2.len();
let interpolate = |array: &[Float], idx: usize| -> Float {
let mut v = 0.;
for i in 0..4 {
if weights[i] != 0. {
v += array[(offset + i) * n2 + idx] * weights[i];
}
}
v
};
let maximum = interpolate(cdf, n2 - 1);
if maximum == 0. {
return (0., 0., 0.);
}
u *= maximum;
let idx = find_interval(n2, |i| interpolate(cdf, i) <= u);
let f0 = interpolate(values, idx);
let f1 = interpolate(values, idx + 1);
let x0 = nodes2[idx];
let x1 = nodes2[idx + 1];
let width = x1 - x0;
let d0 = if idx > 0 {
width * (f1 - interpolate(values, idx - 1)) / (x1 - nodes2[idx - 1])
} else {
f1 - f0
};
let d1 = if idx + 2 < n2 {
width * (interpolate(values, idx + 2) - f0) / (nodes2[idx + 2] - x0)
} else {
f1 - f0
};
u = (u - interpolate(cdf, idx)) / width;
let fhat_coeffs = [
f0,
d0,
-2.0 * d0 - d1 + 3.0 * (f1 - f0),
d0 + d1 + 2.0 * (f0 - f1),
];
let fhat_coeffs_ref = &fhat_coeffs;
let big_fhat_coeffs = [
0.0,
f0,
0.5 * d0,
(1.0 / 3.0) * (-2.0 * d0 - d1) + f1 - f0,
0.25 * (d0 + d1) + 0.5 * (f0 - f1),
];
let big_fhat_coeffs_ref = &big_fhat_coeffs;
let mut big_fhat = 0.0;
let mut fhat = 0.0;
let eval = |t: Float| -> (Float, Float) {
big_fhat = evaluate_polynomial(t, big_fhat_coeffs_ref).unwrap_or(0.);
fhat = evaluate_polynomial(t, fhat_coeffs_ref).unwrap_or(0.);
(big_fhat - u, fhat)
};
let t = newton_bisection(0.0, 1.0, eval);
let sample = x0 + width * t;
let fval = fhat;
let pdf = fhat / maximum;
(sample, fval, pdf)
}
pub fn sample_logistic(u: Float, s: Float) -> Float {
-s * (1. / u - 1.).ln()
}