From d42437e86099e0a839ab6784ec9b52df31765136 Mon Sep 17 00:00:00 2001 From: pingu Date: Thu, 4 Dec 2025 12:55:06 +0000 Subject: [PATCH] Everything is broken --- src/core/bssrdf.rs | 286 +++++++++++++++++++++++++ src/core/bxdf.rs | 364 +++++++++++++++----------------- src/core/film.rs | 2 +- src/core/interaction.rs | 14 +- src/core/material.rs | 452 +++++++++++++++++++++++++++++++++++++--- src/core/mod.rs | 1 + src/core/primitive.rs | 6 +- src/core/texture.rs | 204 ++++++++++++++++-- src/image/io.rs | 51 +++-- src/image/metadata.rs | 2 +- src/shapes/mod.rs | 4 +- src/spectra/rgb.rs | 17 +- src/spectra/sampled.rs | 22 +- src/utils/color.rs | 4 + src/utils/colorspace.rs | 5 - src/utils/math.rs | 101 +++++++++ src/utils/mipmap.rs | 240 +++++++++++++++++++-- src/utils/sampling.rs | 154 +++++++++++++- 18 files changed, 1617 insertions(+), 312 deletions(-) create mode 100644 src/core/bssrdf.rs diff --git a/src/core/bssrdf.rs b/src/core/bssrdf.rs new file mode 100644 index 0000000..df40db9 --- /dev/null +++ b/src/core/bssrdf.rs @@ -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 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, + radius_samples: Vec, + profile: Vec, + rho_eff: Vec, + profile_cdf: Vec, +} + +impl BSSRDFTable { + pub fn new(n_rho_samples: usize, n_radius_samples: usize) -> Self { + let rho_samples: Vec = Vec::with_capacity(n_rho_samples); + let radius_samples: Vec = Vec::with_capacity(n_radius_samples); + let profile: Vec = Vec::with_capacity(n_radius_samples * n_rho_samples); + let rho_eff: Vec = Vec::with_capacity(n_rho_samples); + let profile_cdf: Vec = 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; + 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 { + 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 { + 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!() + } +} diff --git a/src/core/bxdf.rs b/src/core/bxdf.rs index c0637f7..c615298 100644 --- a/src/core/bxdf.rs +++ b/src/core/bxdf.rs @@ -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; + fn sample_f(&self, wo: Vector3f, uc: Float, u: Point2f, f_args: FArgs) -> Option; - 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 { 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>, +#[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>) -> 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::().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 { - 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( &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::() { - specific_bxdf.f(&wo, &wi, mode) - } else { - SampledSpectrum::default() - } + bxdf.f(wo, wi, mode) } - pub fn sample_f_specific( + pub fn sample_f( &self, - wo_render: &Vector3f, + wo_render: Vector3f, u: Float, u2: Point2f, f_args: FArgs, ) -> Option { - 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::() - .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( - &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::() { - 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 { + fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option { 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 { + fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option { 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 { + fn sample_f(&self, wo: Vector3f, uc: Float, u: Point2f, f_args: FArgs) -> Option { 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 { - 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 { + 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 { + fn sample_f(&self, wo: Vector3f, _uc: Float, u: Point2f, f_args: FArgs) -> Option { 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()); diff --git a/src/core/film.rs b/src/core/film.rs index 687a4ea..84dd6cb 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -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(), diff --git a/src/core/interaction.rs b/src/core/interaction.rs index 6aaebf4..3ed8cd1 100644 --- a/src/core/interaction.rs +++ b/src/core/interaction.rs @@ -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>, pub face_index: usize, pub area_light: Option>, - pub material: Option>, + pub material: Option>, 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, - pub bsdf: Option, + // pub bsdf: Option>, } -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, + mtl: Arc, area: Arc, prim_medium_interface: Option>, ray_medium: Arc, diff --git a/src/core/material.rs b/src/core/material.rs index c2bd1ab..b3e3ae4 100644 --- a/src/core/material.rs +++ b/src/core/material.rs @@ -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>, + 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!() + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index bdea598..67e631f 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,5 @@ pub mod aggregates; +pub mod bssrdf; pub mod bxdf; pub mod cie; pub mod film; diff --git a/src/core/primitive.rs b/src/core/primitive.rs index 2c82e58..9007723 100644 --- a/src/core/primitive.rs +++ b/src/core/primitive.rs @@ -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, - material: Arc, + material: Arc, area_light: Arc, medium_interface: Arc, alpha: Option>, @@ -81,7 +81,7 @@ impl PrimitiveTrait for GeometricPrimitive { #[derive(Debug, Clone)] pub struct SimplePrimitiv { shape: Arc, - material: Arc, + material: Arc, } #[derive(Debug, Clone)] diff --git a/src/core/texture.rs b/src/core/texture.rs index 86b844e..641d158 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -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, } @@ -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, } @@ -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, 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::(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>>> = OnceLock::new(); -pub struct ImageTextureBase {} +fn get_texture_cache() -> &'static Mutex>> { + 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, +} + +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 + } +} diff --git a/src/image/io.rs b/src/image/io.rs index 53e1fd9..2d50b11 100644 --- a/src/image/io.rs +++ b/src/image/io.rs @@ -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) -> Result { + pub fn read(path: &Path, encoding: Option) -> Result { let ext = path .extension() .and_then(|s| s.to_str()) @@ -179,7 +181,7 @@ impl Image { } } -fn read_generic(path: &Path, encoding: Option) -> Result { +fn read_generic(path: &Path, encoding: Option) -> Result { // 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) -> Result { 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 { +fn read_exr(path: &Path) -> Result { let image = read_first_rgba_layer_from_file( path, |resolution, _| { @@ -254,16 +257,19 @@ fn read_exr(path: &Path) -> Result { 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 { +fn read_pfm(path: &Path) -> Result { let file = File::open(path)?; let mut reader = BufReader::new(file); @@ -335,11 +341,14 @@ fn read_pfm(path: &Path) -> Result { 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 }) } diff --git a/src/image/metadata.rs b/src/image/metadata.rs index 2569e6d..12c864a 100644 --- a/src/image/metadata.rs +++ b/src/image/metadata.rs @@ -54,7 +54,7 @@ pub struct ImageMetadata { pub full_resolution: Option, pub samples_per_pixel: Option, pub mse: Option, - pub color_space: Option<&'static RGBColorSpace>, + pub colorspace: Option, pub strings: HashMap, pub string_vectors: HashMap>, } diff --git a/src/shapes/mod.rs b/src/shapes/mod.rs index e9c245f..d4a6572 100644 --- a/src/shapes/mod.rs +++ b/src/shapes/mod.rs @@ -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, + mtl: Arc, area: Arc, prim_medium_interface: Option>, ray_medium: Option>, diff --git a/src/spectra/rgb.rs b/src/spectra/rgb.rs index 275e59a..aec2df9 100644 --- a/src/spectra/rgb.rs +++ b/src/spectra/rgb.rs @@ -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])) } } diff --git a/src/spectra/sampled.rs b/src/spectra/sampled.rs index 8d7f724..1ba088c 100644 --- a/src/spectra/sampled.rs +++ b/src/spectra/sampled.rs @@ -68,18 +68,6 @@ impl SampledSpectrum { self.values.iter().sum::() / (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 { diff --git a/src/utils/color.rs b/src/utils/color.rs index 1d8a095..2953e59 100644 --- a/src/utils/color.rs +++ b/src/utils/color.rs @@ -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 for RGB { diff --git a/src/utils/colorspace.rs b/src/utils/colorspace.rs index 91fd68a..6f31058 100644 --- a/src/utils/colorspace.rs +++ b/src/utils/colorspace.rs @@ -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, diff --git a/src/utils/math.rs b/src/utils/math.rs index de30d1e..5fa2b0b 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -243,6 +243,47 @@ pub fn linear_least_squares( Ok((at_ai * atb).transpose()) } +pub fn newton_bisection

(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.); diff --git a/src/utils/mipmap.rs b/src/utils/mipmap.rs index 2adf162..b1013fd 100644 --- a/src/utils/mipmap.rs +++ b/src/utils/mipmap.rs @@ -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, - color_space: Arc, + color_space: Option, wrap_mode: WrapMode, options: MIPMapFilterOptions, } @@ -125,7 +127,7 @@ pub struct MIPMap { impl MIPMap { pub fn new( image: Image, - color_space: Arc, + color_space: Option, wrap_mode: WrapMode, options: MIPMapFilterOptions, ) -> Self { @@ -146,7 +148,7 @@ impl MIPMap { self.pyramid.len() } - pub fn get_rgb_colorspace(&self) -> Arc { + pub fn get_rgb_colorspace(&self) -> Option { 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(&self, _level: usize, _st: Point2i) -> T { - todo!() + fn texel(&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(&self, level: usize, st: Point2f) -> T { @@ -250,11 +259,214 @@ impl MIPMap { fn ewa( &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::(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 { + 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., +]; diff --git a/src/utils/sampling.rs b/src/utils/sampling.rs index b336bee..646ed61 100644 --- a/src/utils/sampling.rs +++ b/src/utils/sampling.rs @@ -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() }