Compare commits

..

4 commits

159 changed files with 13057 additions and 8032 deletions

3
.gitignore vendored
View file

@ -1,7 +1,8 @@
/target target/
*.lock *.lock
*.log *.log
*.bak *.bak
flip.rs flip.rs
.vscode .vscode
rust-analyzer.json rust-analyzer.json
data/

View file

@ -37,6 +37,9 @@ cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default
ptex = "0.3.0" ptex = "0.3.0"
ptex-sys = "0.3.0" ptex-sys = "0.3.0"
slice = "0.0.4" slice = "0.0.4"
crossbeam-channel = "0.5.15"
num_cpus = "1.17.0"
ply-rs = "0.1.3"
[build-dependencies] [build-dependencies]
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true } spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }

View file

@ -15,6 +15,8 @@ num-traits = "0.2.19"
once_cell = "1.21.3" once_cell = "1.21.3"
smallvec = "1.15.1" smallvec = "1.15.1"
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
half = "2.7.1"
rand = "0.9.2"
[features] [features]
use_f64 = [] use_f64 = []

470
shared/src/bxdfs/complex.rs Normal file
View file

@ -0,0 +1,470 @@
use crate::core::bsdf::BSDF;
use crate::core::bxdf::{
BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::color::RGB;
use crate::core::geometry::{
Normal3f, Point2f, Vector3f, abs_cos_theta, cos_theta, same_hemisphere,
};
use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect,
refract,
};
use crate::spectra::{RGBUnboundedSpectrum, SampledSpectrum, StandardColorSpaces};
use crate::utils::math::{
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
square, trimmed_logistic,
};
use crate::utils::sampling::{
cosine_hemisphere_pdf, sample_cosine_hemisphere, sample_trimmed_logistic,
};
use crate::{Float, INV_2_PI, INV_PI, PI};
use core::any::Any;
static P_MAX: usize = 3;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct HairBxDF {
pub h: Float,
pub eta: Float,
pub sigma_a: SampledSpectrum,
pub beta_m: Float,
pub beta_n: Float,
pub v: [Float; P_MAX + 1],
pub s: Float,
pub sin_2k_alpha: [Float; P_MAX],
pub cos_2k_alpha: [Float; P_MAX],
pub colorspaces: StandardColorSpaces,
}
impl HairBxDF {
pub fn new(
h: Float,
eta: Float,
sigma_a: SampledSpectrum,
beta_m: Float,
beta_n: Float,
alpha: Float,
colorspaces: StandardColorSpaces,
) -> Self {
let mut sin_2k_alpha = [0.; P_MAX];
let mut cos_2k_alpha = [0.; P_MAX];
sin_2k_alpha[0] = radians(alpha).sin();
cos_2k_alpha[0] = safe_sqrt(1. - square(sin_2k_alpha[0]));
for i in 0..P_MAX {
sin_2k_alpha[i] = 2. * cos_2k_alpha[i - 1] * sin_2k_alpha[i - 1];
cos_2k_alpha[i] = square(cos_2k_alpha[i - 1]) - square(sin_2k_alpha[i - 1]);
}
Self {
h,
eta,
sigma_a,
beta_m,
beta_n,
v: [0.; P_MAX + 1],
s: 0.,
sin_2k_alpha,
cos_2k_alpha,
colorspaces,
}
}
fn ap(
cos_theta_o: Float,
eta: Float,
h: Float,
t: SampledSpectrum,
) -> [SampledSpectrum; P_MAX + 1] {
let cos_gamma_o = safe_sqrt(1. - square(h));
let cos_theta = cos_theta_o * cos_gamma_o;
let f = fr_dielectric(cos_theta, eta);
let ap0 = SampledSpectrum::new(f);
let ap1 = t * (1.0 - f).powi(2);
let tf = t * f;
std::array::from_fn(|p| match p {
0 => ap0,
1 => ap1,
_ if p < P_MAX => ap1 * tf.pow_int(p - 1),
_ => ap1 * tf.pow_int(p - 1) / (SampledSpectrum::new(1.0) - tf),
})
}
fn mp(
cos_theta_i: Float,
cos_theta_o: Float,
sin_theta_i: Float,
sin_theta_o: Float,
v: Float,
) -> Float {
let a = cos_theta_i * cos_theta_o / v;
let b = sin_theta_i * sin_theta_o / v;
if v <= 0.1 {
fast_exp(log_i0(a) - b - 1. / v + 0.6931 + (1. / (2. * v).ln()))
} else {
fast_exp(-b) * i0(a) / ((1. / v).sinh() * 2. * v)
}
}
fn np(phi: Float, p: i32, s: Float, gamma_o: Float, gamma_t: Float) -> Float {
let mut dphi = phi - Self::phi(p, gamma_o, gamma_t);
while dphi > PI {
dphi -= 2. * PI;
}
while dphi < -PI {
dphi += 2. * PI;
}
trimmed_logistic(dphi, s, -PI, PI)
}
fn phi(p: i32, gamma_o: Float, gamma_t: Float) -> Float {
2. * p as Float * gamma_t - 2. * gamma_o + p as Float * PI
}
fn ap_pdf(&self, cos_theta_o: Float) -> [Float; P_MAX + 1] {
let sin_theta_o = safe_sqrt(1. - square(cos_theta_o));
let sin_theta_t = sin_theta_o / self.eta;
let cos_theta_t = safe_sqrt(1. - square(sin_theta_t));
let etap = safe_sqrt(square(self.eta) - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
let cos_gamma_t = safe_sqrt(1. - square(sin_gamma_t));
// let gamma_t = safe_asin(sin_gamma_t);
let t_value = -self.sigma_a * (2. * cos_gamma_t / cos_theta_t);
let t = t_value.exp();
let ap = Self::ap(cos_theta_o, self.eta, self.h, t);
let sum_y: Float = ap.iter().map(|s| s.average()).sum();
std::array::from_fn(|i| ap[i].average() / sum_y)
}
pub fn sigma_a_from_concentration(&self, ce: Float, cp: Float) -> RGBUnboundedSpectrum {
let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37);
let pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05);
let sigma_a = ce * eumelanin_sigma_a + cp * pheomelanin_sigma_a;
RGBUnboundedSpectrum::new(&self.colorspaces.srgb, sigma_a)
}
}
impl BxDFTrait for HairBxDF {
fn flags(&self) -> BxDFFlags {
BxDFFlags::GLOSSY_REFLECTION
}
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));
let phi_o = wo.z().atan2(wo.y());
let gamma_o = safe_asin(self.h);
// Compute hair coordinate system terms related to wi
let sin_theta_i = wi.x();
let cos_theta_i = safe_sqrt(1. - square(sin_theta_i));
let phi_i = wi.z().atan2(wi.y());
let sin_theta_t = sin_theta_o / self.eta;
let cos_theta_t = safe_sqrt(1. - square(sin_theta_t));
let etap = safe_sqrt(square(self.eta) - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
let cos_gamma_t = safe_sqrt(1. - square(sin_gamma_t));
let gamma_t = safe_asin(sin_gamma_t);
let sampled_value = -self.sigma_a * (2. * cos_gamma_t / cos_theta_t);
let t = sampled_value.exp();
let phi = phi_i - phi_o;
let ap_pdf = Self::ap(cos_theta_o, self.eta, self.h, t);
let mut f_sum = SampledSpectrum::new(0.);
for (p, &ap) in ap_pdf.iter().enumerate().take(P_MAX) {
let (sin_thetap_o, cos_thetap_o) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
f_sum += Self::mp(
cos_theta_i,
cos_thetap_o,
sin_theta_i,
sin_thetap_o,
self.v[p],
) * 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);
}
f_sum
}
fn sample_f(
&self,
wo: Vector3f,
mut uc: Float,
u: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
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());
let gamma_o = safe_asin(self.h);
// Determine which term to sample for hair scattering
let ap_pdf = self.ap_pdf(cos_theta_o);
let p = sample_discrete(&ap_pdf, uc, None, Some(&mut uc));
let (sin_thetap_o, mut cos_thetap_o) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
cos_thetap_o = cos_thetap_o.abs();
let cos_theta =
1. + self.v[p] * (u[0].max(1e-5) + (1. - u[0]) * fast_exp(-2. / self.v[p])).ln();
let sin_theta = safe_sqrt(1. - square(cos_theta));
let cos_phi = (2. * PI * u[1]).cos();
let sin_theta_i = -cos_theta * sin_thetap_o + sin_theta * cos_phi * cos_thetap_o;
let cos_theta_i = safe_sqrt(1. - square(sin_theta_i));
let etap = safe_sqrt(square(self.eta) - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
// let cos_gamma_t = safe_sqrt(1. - square(sin_gamma_t));
let gamma_t = safe_asin(sin_gamma_t);
let dphi = if p < P_MAX {
Self::phi(p as i32, gamma_o, gamma_t) + sample_trimmed_logistic(uc, self.s, -PI, PI)
} else {
2. * PI * uc
};
let phi_i = phi_o + dphi;
let wi = Vector3f::new(
sin_theta_i,
cos_theta_i * phi_i.cos(),
cos_theta_i * phi_i.sin(),
);
let mut pdf = 0.;
for (p, &ap) in ap_pdf.iter().enumerate().take(P_MAX) {
let (sin_thetap_o, cos_thetap_o_raw) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
let cos_thetap_o = cos_thetap_o_raw.abs();
pdf += Self::mp(
cos_theta_i,
cos_thetap_o,
sin_theta_i,
sin_thetap_o,
self.v[p],
) * ap
* Self::np(dphi, p as i32, self.s, gamma_o, gamma_t);
}
pdf += Self::mp(
cos_theta_i,
cos_theta_o,
sin_theta_i,
sin_theta_o,
self.v[P_MAX],
) * ap_pdf[P_MAX]
* INV_2_PI;
let bsd = BSDFSample {
f: self.f(wo, wi, f_args.mode),
wi,
pdf,
flags: self.flags(),
..Default::default()
};
Some(bsd)
}
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());
let gamma_o = safe_asin(self.h);
// Determine which term to sample for hair scattering
let sin_theta_i = wi.x();
let cos_theta_i = safe_sqrt(1. - square(sin_theta_i));
let phi_i = wi.z().atan2(wi.y());
// Compute $\gammat$ for refracted ray
let etap = safe_sqrt(self.eta * self.eta - square(sin_theta_o)) / cos_theta_o;
let sin_gamma_t = self.h / etap;
let gamma_t = safe_asin(sin_gamma_t);
// Compute PDF for $A_p$ terms
let ap_pdf = self.ap_pdf(cos_theta_o);
let phi = phi_i - phi_o;
let mut pdf = 0.;
for (p, &ap) in ap_pdf.iter().enumerate().take(P_MAX) {
let (sin_thetap_o, raw_cos_thetap_o) = match p {
0 => (
sin_theta_o * self.cos_2k_alpha[1] - cos_theta_o * self.sin_2k_alpha[1],
cos_theta_o * self.cos_2k_alpha[1] + sin_theta_o * self.sin_2k_alpha[1],
),
1 => (
sin_theta_o * self.cos_2k_alpha[0] + cos_theta_o * self.sin_2k_alpha[0],
cos_theta_o * self.cos_2k_alpha[0] - sin_theta_o * self.sin_2k_alpha[0],
),
2 => (
sin_theta_o * self.cos_2k_alpha[2] + cos_theta_o * self.sin_2k_alpha[2],
cos_theta_o * self.cos_2k_alpha[2] - sin_theta_o * self.sin_2k_alpha[2],
),
_ => (sin_theta_o, cos_theta_o),
};
let cos_thetap_o = raw_cos_thetap_o.abs();
pdf += Self::mp(
cos_theta_i,
cos_thetap_o,
sin_theta_i,
sin_thetap_o,
self.v[p],
) * ap
* Self::np(phi, p as i32, self.s, gamma_o, gamma_t);
}
pdf += Self::mp(
cos_theta_i,
cos_theta_o,
sin_theta_i,
sin_theta_o,
self.v[P_MAX],
) * ap_pdf[P_MAX]
* INV_2_PI;
pdf
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct NormalizedFresnelBxDF {
pub eta: Float,
}
impl BxDFTrait for NormalizedFresnelBxDF {
fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
if !same_hemisphere(wo, wi) {
return SampledSpectrum::new(0.);
}
let c = 1. - 2. * fresnel_moment1(1. / self.eta);
let mut f = SampledSpectrum::new((1. - fr_dielectric(cos_theta(wi), self.eta)) / (c * PI));
if mode == TransportMode::Radiance {
f /= square(self.eta)
}
f
}
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) {
return None;
}
let mut wi = sample_cosine_hemisphere(u);
if wo.z() < 0. {
wi[2] *= -1.;
}
Some(BSDFSample {
f: self.f(wo, wi, f_args.mode),
wi,
pdf: self.pdf(wo, wi, f_args),
flags: BxDFFlags::DIFFUSE_REFLECTION,
..Default::default()
})
}
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) {
return 0.;
}
abs_cos_theta(wi) * INV_PI
}
fn flags(&self) -> BxDFFlags {
BxDFFlags::REFLECTION | BxDFFlags::DIFFUSE
}
fn regularize(&mut self) {
return;
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
pub struct EmptyBxDF;
impl BxDFTrait for EmptyBxDF {
fn f(&self, _wo: Vector3f, _wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
SampledSpectrum::default()
}
fn sample_f(
&self,
_wo: Vector3f,
_u: Float,
_u2: Point2f,
_f_args: FArgs,
) -> Option<BSDFSample> {
None
}
fn pdf(&self, _wo: Vector3f, _wi: Vector3f, _f_args: FArgs) -> Float {
0.0
}
fn flags(&self) -> BxDFFlags {
BxDFFlags::UNSET
}
fn regularize(&mut self) {
return;
}
fn as_any(&self) -> &dyn Any {
self
}
}

View file

@ -0,0 +1,154 @@
use crate::core::bxdf::{
BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::geometry::{
Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, same_hemisphere,
};
use crate::core::scattering::{TrowbridgeReitzDistribution, fr_complex_from_spectrum, reflect};
use crate::spectra::SampledSpectrum;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ConductorBxDF {
pub mf_distrib: TrowbridgeReitzDistribution,
pub eta: SampledSpectrum,
pub k: SampledSpectrum,
}
unsafe impl Send for ConductorBxDF {}
unsafe impl Sync for ConductorBxDF {}
impl ConductorBxDF {
pub fn new(
mf_distrib: &TrowbridgeReitzDistribution,
eta: SampledSpectrum,
k: SampledSpectrum,
) -> Self {
Self {
mf_distrib: *mf_distrib,
eta,
k,
}
}
}
impl BxDFTrait for ConductorBxDF {
fn flags(&self) -> BxDFFlags {
if self.mf_distrib.effectively_smooth() {
BxDFFlags::SPECULAR_REFLECTION
} else {
BxDFFlags::GLOSSY_REFLECTION
}
}
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) {
return None;
}
if self.mf_distrib.effectively_smooth() {
let wi = Vector3f::new(-wo.x(), -wo.y(), wo.z());
let f =
fr_complex_from_spectrum(abs_cos_theta(wi), self.eta, self.k) / abs_cos_theta(wi);
let bsdf = BSDFSample {
f,
wi,
pdf: 1.,
flags: BxDFFlags::SPECULAR_REFLECTION,
..Default::default()
};
return Some(bsdf);
}
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) {
return None;
}
let pdf = self.mf_distrib.pdf(wo, wm) / (4. * wo.dot(wm).abs());
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)
/ (4. * cos_theta_i * cos_theta_o);
let bsdf = BSDFSample {
f,
wi,
pdf,
flags: BxDFFlags::GLOSSY_REFLECTION,
..Default::default()
};
Some(bsdf)
}
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);
if cos_theta_i == 0. || cos_theta_o == 0. {
return SampledSpectrum::new(0.);
}
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)
/ (4. * cos_theta_i * cos_theta_o)
}
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) {
return 0.;
}
if self.mf_distrib.effectively_smooth() {
return 0.;
}
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())
}
fn regularize(&mut self) {
self.mf_distrib.regularize();
}
fn as_any(&self) -> &dyn Any {
self
}
}

View file

@ -0,0 +1,371 @@
use crate::core::bxdf::{
BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::geometry::{
Normal3f, Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere,
};
use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, reflect, refract,
};
use crate::spectra::SampledSpectrum;
use crate::utils::math::square;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DielectricBxDF {
pub eta: Float,
pub mf_distrib: TrowbridgeReitzDistribution,
}
impl DielectricBxDF {
pub fn new(eta: Float, mf_distrib: TrowbridgeReitzDistribution) -> Self {
Self { eta, mf_distrib }
}
}
impl BxDFTrait for DielectricBxDF {
fn flags(&self) -> BxDFFlags {
let flags = if self.eta == 1. {
BxDFFlags::TRANSMISSION
} else {
BxDFFlags::REFLECTION | BxDFFlags::TRANSMISSION
};
flags
| if self.mf_distrib.effectively_smooth() {
BxDFFlags::SPECULAR
} else {
BxDFFlags::GLOSSY
}
}
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 reflect = cos_theta_i * cos_theta_o > 0.;
let mut etap = 1.;
if !reflect {
etap = if cos_theta_o > 0. {
self.eta
} else {
1. / self.eta
};
}
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. {
return SampledSpectrum::new(0.);
}
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
/ (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 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();
if mode == TransportMode::Radiance {
ft /= square(etap)
}
SampledSpectrum::new(ft)
}
}
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 reflect = cos_theta_i * cos_theta_o > 0.;
let mut etap = 1.;
if !reflect {
etap = if cos_theta_o > 0. {
self.eta
} else {
1. / self.eta
};
}
let wm_orig = wi * etap + wo;
if cos_theta_i == 0. || cos_theta_o == 0. || wm_orig.norm_squared() == 0. {
return 0.;
}
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. {
return 0.;
}
let r = fr_dielectric(wo.dot(wm.into()), self.eta);
let t = 1. - r;
let mut pr = r;
let mut pt = t;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
if pr == 0. && pt == 0. {
return 0.;
}
if reflect {
self.mf_distrib.pdf(
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)
}
}
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 t = 1. - r;
let mut pr = r;
let mut pt = t;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
// If probabilities are null, doesnt contribute
if pr == 0. && pt == 0. {
return None;
}
if uc < pr / (pr + pt) {
let wi = Vector3f::new(-wo.x(), -wo.y(), wo.z());
let fr = SampledSpectrum::new(r / abs_cos_theta(wi));
let bsdf = BSDFSample {
f: fr,
wi,
pdf: pr / (pr + pt),
flags: BxDFFlags::SPECULAR_REFLECTION,
..Default::default()
};
Some(bsdf)
} else {
// Compute ray direction for specular transmission
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);
}
let bsdf = BSDFSample {
f: ft,
wi,
pdf: pt / (pr + pt),
flags: BxDFFlags::SPECULAR_TRANSMISSION,
eta: etap,
..Default::default()
};
Some(bsdf)
} else {
None
}
}
} else {
// Sample rough dielectric BSDF
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;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
if pr == 0. && pt == 0. {
return None;
}
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) {
return None;
}
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)),
);
let bsdf = BSDFSample {
f,
wi,
pdf,
flags: BxDFFlags::GLOSSY_REFLECTION,
..Default::default()
};
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. {
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);
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),
);
if f_args.mode == TransportMode::Radiance {
ft /= square(etap);
}
let bsdf = BSDFSample {
f: ft,
wi,
pdf,
flags: BxDFFlags::GLOSSY_TRANSMISSION,
eta: etap,
..Default::default()
};
Some(bsdf)
}
} else {
None
}
}
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
self.mf_distrib.regularize();
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct ThinDielectricBxDF {
pub eta: Float,
}
impl ThinDielectricBxDF {
pub fn new(eta: Float) -> Self {
Self { eta }
}
}
impl BxDFTrait for ThinDielectricBxDF {
fn flags(&self) -> BxDFFlags {
BxDFFlags::REFLECTION | BxDFFlags::TRANSMISSION | BxDFFlags::SPECULAR
}
fn f(&self, _wo: Vector3f, _wi: Vector3f, _mode: TransportMode) -> SampledSpectrum {
SampledSpectrum::new(0.)
}
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);
let mut t = 1. - r;
if r < 1. {
r += square(t) * r / (1. - square(r));
t = 1. - r;
}
let mut pr = r;
let mut pt = t;
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
let transmission_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::TRANSMISSION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
pr = 0.;
}
if !f_args.sample_flags.contains(transmission_flags) {
pt = 0.;
}
if pr == 0. && pt == 0. {
return None;
}
if uc < pr / (pr + pt) {
let wi = Vector3f::new(-wo.x(), -wo.y(), wo.z());
let f = SampledSpectrum::new(r / abs_cos_theta(wi));
let bsdf = BSDFSample {
f,
wi,
pdf: pr / (pr + pt),
flags: BxDFFlags::SPECULAR_REFLECTION,
..Default::default()
};
Some(bsdf)
} else {
// Perfect specular transmission
let wi = -wo;
let f = SampledSpectrum::new(t / abs_cos_theta(wi));
let bsdf = BSDFSample {
f,
wi,
pdf: pr / (pr + pt),
flags: BxDFFlags::SPECULAR_TRANSMISSION,
..Default::default()
};
Some(bsdf)
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
todo!()
}
}

View file

@ -0,0 +1,79 @@
use crate::core::bxdf::{
BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::geometry::{Point2f, Vector3f, abs_cos_theta, same_hemisphere};
use crate::spectra::SampledSpectrum;
use crate::utils::sampling::{cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DiffuseBxDF {
pub r: SampledSpectrum,
}
impl DiffuseBxDF {
pub fn new(r: SampledSpectrum) -> Self {
Self { r }
}
}
impl BxDFTrait for DiffuseBxDF {
fn flags(&self) -> BxDFFlags {
if !self.r.is_black() {
BxDFFlags::DIFFUSE_REFLECTION
} else {
BxDFFlags::UNSET
}
}
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> {
let reflection_flags =
BxDFReflTransFlags::from_bits_truncate(BxDFReflTransFlags::REFLECTION.bits());
if !f_args.sample_flags.contains(reflection_flags) {
return None;
}
let mut wi = sample_cosine_hemisphere(u);
if wo.z() == 0. {
wi[2] *= -1.;
}
let pdf = cosine_hemisphere_pdf(abs_cos_theta(wi));
let bsdf = BSDFSample {
f: self.r * INV_PI,
wi,
pdf,
flags: BxDFFlags::DIFFUSE_REFLECTION,
..Default::default()
};
Some(bsdf)
}
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) {
return 0.;
}
cosine_hemisphere_pdf(abs_cos_theta(wi))
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
return;
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DiffuseTransmissionBxDF;

646
shared/src/bxdfs/layered.rs Normal file
View file

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

View file

@ -0,0 +1,241 @@
use crate::core::bxdf::{
BSDFSample, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode,
};
use crate::core::geometry::{
Point2f, Vector3f, VectorLike, abs_cos_theta, cos_theta, same_hemisphere, spherical_direction,
spherical_theta,
};
use crate::core::scattering::reflect;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::square;
use crate::utils::ptr::{Ptr, Slice};
use crate::utils::sampling::{PiecewiseLinear2D, cosine_hemisphere_pdf, sample_cosine_hemisphere};
use crate::{Float, INV_PI, PI, PI_OVER_2};
use core::any::Any;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDFData {
pub wavelengths: Slice<Float>,
pub spectra: PiecewiseLinear2D<3>,
pub ndf: PiecewiseLinear2D<0>,
pub vndf: PiecewiseLinear2D<2>,
pub sigma: PiecewiseLinear2D<0>,
pub isotropic: bool,
pub luminance: PiecewiseLinear2D<2>,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDF {
pub brdf: Ptr<MeasuredBxDFData>,
pub lambda: SampledWavelengths,
}
unsafe impl Send for MeasuredBxDF {}
unsafe impl Sync for MeasuredBxDF {}
impl MeasuredBxDF {
pub fn new(brdf: &MeasuredBxDFData, lambda: &SampledWavelengths) -> Self {
Self {
brdf: Ptr::from(brdf),
lambda: *lambda,
}
}
pub fn theta2u(theta: Float) -> Float {
(theta * (2. / PI)).sqrt()
}
pub fn phi2u(phi: Float) -> Float {
phi * 1. / (2. * PI) + 0.5
}
pub fn u2theta(u: Float) -> Float {
square(u) * PI_OVER_2
}
pub fn u2phi(u: Float) -> Float {
(2. * u - 1.) * PI
}
}
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) {
return SampledSpectrum::new(0.);
}
let mut wo_curr = wo;
let mut wi_curr = wi;
if wo.z() < 0. {
wo_curr = -wo_curr;
wi_curr = -wi_curr;
}
// Get half direction vector
let wm_curr = wi_curr + wo_curr;
if wm_curr.norm_squared() == 0. {
return SampledSpectrum::new(0.);
}
let wm = wm_curr.normalize();
// Map vectors to unit square
let theta_o = spherical_theta(wo_curr);
let phi_o = wo_curr.y().atan2(wo_curr.x());
let theta_m = spherical_theta(wm);
let phi_m = wm.y().atan2(wm.x());
let u_wo = Point2f::new(MeasuredBxDF::theta2u(theta_o), MeasuredBxDF::phi2u(phi_o));
let u_wm_phi = if self.brdf.isotropic {
phi_m - phi_o
} else {
phi_m
};
let mut u_wm = Point2f::new(
MeasuredBxDF::theta2u(theta_m),
MeasuredBxDF::phi2u(u_wm_phi),
);
u_wm[1] -= u_wm[1].floor();
// Inverse parametrization
let ui = self.brdf.vndf.invert(u_wm, [phi_o, theta_o]);
let fr = SampledSpectrum::from_fn(|i| {
self.brdf
.spectra
.evaluate(ui.p, [phi_o, theta_o, self.lambda[i]])
.max(0.0)
});
fr * self.brdf.ndf.evaluate(u_wm, [])
/ (4. * self.brdf.sigma.evaluate(u_wo, []) * cos_theta(wi))
}
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) {
return 0.;
}
let mut wo_curr = wo;
let mut wi_curr = wi;
if wo.z() < 0. {
wo_curr = -wo_curr;
wi_curr = -wi_curr;
}
let wm_curr = wi_curr + wo_curr;
if wm_curr.norm_squared() == 0. {
return 0.;
}
let wm = wm_curr.normalize();
let theta_o = spherical_theta(wo_curr);
let phi_o = wo_curr.y().atan2(wo_curr.x());
let theta_m = spherical_theta(wm);
let phi_m = wm.y().atan2(wm.x());
let u_wm_phi = if self.brdf.isotropic {
phi_m - phi_o
} else {
phi_m
};
let mut u_wm = Point2f::new(
MeasuredBxDF::theta2u(theta_m),
MeasuredBxDF::phi2u(u_wm_phi),
);
u_wm[1] = u_wm[1] - u_wm[1].floor();
let ui = self.brdf.vndf.invert(u_wm, [phi_o, theta_o]);
let sample = ui.p;
let vndf_pdf = ui.pdf;
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);
vndf_pdf * pdf / jacobian
}
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) {
return None;
}
let mut flip_w = false;
let mut wo_curr = wo;
if wo.z() <= 0. {
wo_curr = -wo_curr;
flip_w = true;
}
let theta_o = spherical_theta(wo_curr);
let phi_o = wo_curr.y().atan2(wo_curr.x());
// Warp sample using luminance distribution
let mut s = self.brdf.luminance.sample(u, [phi_o, theta_o]);
let u = s.p;
let lum_pdf = s.pdf;
// Sample visible normal distribution of measured BRDF
s = self.brdf.vndf.sample(u, [phi_o, theta_o]);
let u_wm = s.p;
let mut pdf = s.pdf;
// Map from microfacet normal to incident direction
let mut phi_m = MeasuredBxDF::u2phi(u_wm.y());
let theta_m = MeasuredBxDF::u2theta(u_wm.x());
if self.brdf.isotropic {
phi_m += phi_o;
}
let sin_theta_m = theta_m.sin();
let cos_theta_m = theta_m.cos();
let wm = spherical_direction(sin_theta_m, cos_theta_m, phi_m);
let mut wi = reflect(wo_curr, wm.into());
if wi.z() <= 0. {
return None;
}
// Interpolate spectral BRDF
let mut f = SampledSpectrum::from_fn(|i| {
self.brdf
.spectra
.evaluate(u, [phi_o, theta_o, self.lambda[i]])
.max(0.0)
});
let u_wo = Point2f::new(MeasuredBxDF::theta2u(theta_o), MeasuredBxDF::phi2u(phi_o));
f *= self.brdf.ndf.evaluate(u_wm, [])
/ (4. * self.brdf.sigma.evaluate(u_wo, []) * abs_cos_theta(wi));
pdf /= 4. * wm.dot(wo_curr) * f32::max(2. * square(PI) * u_wm.x(), 1e-6);
if flip_w {
wi = -wi;
}
let bsdf = BSDFSample {
f,
wi,
pdf: pdf * lum_pdf,
flags: BxDFFlags::GLOSSY_REFLECTION,
..Default::default()
};
Some(bsdf)
}
fn as_any(&self) -> &dyn Any {
self
}
fn regularize(&mut self) {
return;
}
}

13
shared/src/bxdfs/mod.rs Normal file
View file

@ -0,0 +1,13 @@
pub mod complex;
pub mod conductor;
pub mod dielectric;
pub mod diffuse;
pub mod layered;
pub mod measured;
pub use complex::{EmptyBxDF, HairBxDF, NormalizedFresnelBxDF};
pub use conductor::ConductorBxDF;
pub use dielectric::{DielectricBxDF, ThinDielectricBxDF};
pub use diffuse::{DiffuseBxDF, DiffuseTransmissionBxDF};
pub use layered::{CoatedConductorBxDF, CoatedDiffuseBxDF};
pub use measured::{MeasuredBxDF, MeasuredBxDFData};

View file

@ -6,7 +6,6 @@ use crate::core::geometry::{
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::images::ImageMetadata;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
@ -42,11 +41,12 @@ impl OrthographicCamera {
-screen_window.p_max.y(), -screen_window.p_max.y(),
0., 0.,
)); ));
let film_ptr = base.film;
if film_ptr.is_null() { let mut base_ortho = base;
let film = base.film;
if film.is_null() {
panic!("Camera must have a film"); panic!("Camera must have a film");
} }
let film = unsafe { &*film_ptr };
let raster_from_ndc = Transform::scale( let raster_from_ndc = Transform::scale(
film.full_resolution().x() as Float, film.full_resolution().x() as Float,
@ -60,7 +60,6 @@ impl OrthographicCamera {
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster; let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.)); let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.)); let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
let mut base_ortho = base;
base_ortho.min_dir_differential_x = Vector3f::new(0., 0., 0.); base_ortho.min_dir_differential_x = Vector3f::new(0., 0., 0.);
base_ortho.min_dir_differential_y = Vector3f::new(0., 0., 0.); base_ortho.min_dir_differential_y = Vector3f::new(0., 0., 0.);
base_ortho.min_pos_differential_x = dx_camera; base_ortho.min_pos_differential_x = dx_camera;
@ -80,11 +79,6 @@ impl OrthographicCamera {
} }
impl CameraTrait for OrthographicCamera { impl CameraTrait for OrthographicCamera {
#[cfg(not(target_os = "cuda"))]
fn init_metadata(&self, metadata: &mut ImageMetadata) {
self.base.init_metadata(metadata)
}
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
@ -102,7 +96,7 @@ impl CameraTrait for OrthographicCamera {
p_camera, p_camera,
Vector3f::new(0., 0., 1.), Vector3f::new(0., 0., 1.),
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium.clone(), &*self.base().medium,
); );
if self.lens_radius > 0. { if self.lens_radius > 0. {
let p_lens_vec = let p_lens_vec =
@ -133,11 +127,11 @@ impl CameraTrait for OrthographicCamera {
let mut rd = RayDifferential::default(); let mut rd = RayDifferential::default();
if self.lens_radius > 0.0 { if self.lens_radius > 0.0 {
let mut sample_x = sample; let mut sample_x = sample;
sample_x.p_film.x += 1.0; sample_x.p_film[0] += 1.0;
let rx = self.generate_ray(sample_x, lambda)?; let rx = self.generate_ray(sample_x, lambda)?;
let mut sample_y = sample; let mut sample_y = sample;
sample_y.p_film.y += 1.0; sample_y.p_film[1] += 1.0;
let ry = self.generate_ray(sample_y, lambda)?; let ry = self.generate_ray(sample_y, lambda)?;
rd.rx_origin = rx.ray.o; rd.rx_origin = rx.ray.o;
@ -161,7 +155,7 @@ impl CameraTrait for OrthographicCamera {
rd.rx_direction = central_cam_ray.ray.d; rd.rx_direction = central_cam_ray.ray.d;
rd.ry_direction = central_cam_ray.ray.d; rd.ry_direction = central_cam_ray.ray.d;
} }
central_cam_ray.ray.differential = Some(rd); central_cam_ray.ray.differential = rd;
Some(central_cam_ray) Some(central_cam_ray)
} }
} }

View file

@ -1,5 +1,6 @@
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::filter::FilterTrait;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike, Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
}; };
@ -10,7 +11,8 @@ use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::utils::transform::Transform; use crate::utils::transform::Transform;
#[derive(Debug)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct PerspectiveCamera { pub struct PerspectiveCamera {
pub base: CameraBase, pub base: CameraBase,
pub screen_from_camera: Transform, pub screen_from_camera: Transform,
@ -77,12 +79,7 @@ impl PerspectiveCamera {
} }
} }
impl PerspectiveCamera { impl CameraTrait for PerspectiveCamera {
#[cfg(not(target_os = "cuda"))]
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
@ -101,7 +98,7 @@ impl PerspectiveCamera {
Point3f::new(0., 0., 0.), Point3f::new(0., 0., 0.),
p_vector.normalize(), p_vector.normalize(),
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium.clone(), &*self.base().medium,
); );
// Modify ray for depth of field // Modify ray for depth of field
if self.lens_radius > 0. { if self.lens_radius > 0. {

View file

@ -1,15 +1,15 @@
use crate::PI; use crate::PI;
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::color::SRGB;
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike, Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
}; };
use crate::core::image::{Image, PixelFormat};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::core::scattering::refract; use crate::core::scattering::refract;
use crate::images::{Image, PixelFormat};
use crate::spectra::color::SRGB;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{lerp, quadratic, square}; use crate::utils::math::{lerp, quadratic, square};
@ -385,32 +385,15 @@ impl RealisticCamera {
} }
impl CameraTrait for RealisticCamera { impl CameraTrait for RealisticCamera {
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
fn get_film(&self) -> &Film {
#[cfg(not(target_os = "cuda"))]
{
if self.base.film.is_null() {
panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
}
}
unsafe { &*self.base.film }
}
fn generate_ray( fn generate_ray(
&self, &self,
sample: CameraSample, sample: CameraSample,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<CameraRay> { ) -> Option<CameraRay> {
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
let film = self.get_film(); let film = self.get_film();
let s = Point2f::new( let s = Point2f::new(
sample.p_film.x() / film.full_resolution().x() as Float, sample.p_film.x() / film.full_resolution().x() as Float,

View file

@ -1,4 +1,4 @@
use crate::core::camera::{CameraBase, CameraRay, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction}; use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
use crate::core::medium::Medium; use crate::core::medium::Medium;
@ -6,7 +6,6 @@ use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square}; use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@ -15,36 +14,18 @@ pub enum Mapping {
EqualArea, EqualArea,
} }
#[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SphericalCamera { pub struct SphericalCamera {
pub mapping: Mapping, pub mapping: Mapping,
pub base: CameraBase, pub base: CameraBase,
} }
#[cfg(not(target_os = "cuda"))]
impl SphericalCamera {
pub fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
}
impl CameraTrait for SphericalCamera { impl CameraTrait for SphericalCamera {
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
fn get_film(&self) -> &Film {
#[cfg(not(target_os = "cuda"))]
{
if self.base.film.is_null() {
panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
}
}
unsafe { &*self.base.film }
}
fn generate_ray( fn generate_ray(
&self, &self,
sample: CameraSample, sample: CameraSample,
@ -58,7 +39,6 @@ impl CameraTrait for SphericalCamera {
); );
let dir: Vector3f; let dir: Vector3f;
if self.mapping == Mapping::EquiRectangular { if self.mapping == Mapping::EquiRectangular {
// Compute ray direction using equirectangular mapping
let theta = PI * uv[1]; let theta = PI * uv[1];
let phi = 2. * PI * uv[0]; let phi = 2. * PI * uv[0];
dir = spherical_direction(theta.sin(), theta.cos(), phi); dir = spherical_direction(theta.sin(), theta.cos(), phi);
@ -67,7 +47,7 @@ impl CameraTrait for SphericalCamera {
uv = wrap_equal_area_square(&mut uv); uv = wrap_equal_area_square(&mut uv);
dir = equal_area_square_to_sphere(uv); dir = equal_area_square_to_sphere(uv);
} }
std::mem::swap(&mut dir.y(), &mut dir.z()); core::mem::swap(&mut dir.y(), &mut dir.z());
let ray = Ray::new( let ray = Ray::new(
Point3f::new(0., 0., 0.), Point3f::new(0., 0., 0.),

125
shared/src/core/bsdf.rs Normal file
View file

@ -0,0 +1,125 @@
use crate::Float;
use crate::core::bxdf::{BSDFSample, BxDF, BxDFFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::geometry::{Frame, Normal3f, Point2f, Vector3f, VectorLike};
use crate::spectra::SampledSpectrum;
use crate::utils::Ptr;
#[repr(C)]
#[derive(Copy, Debug, Default)]
pub struct BSDF {
bxdf: Ptr<BxDF>,
shading_frame: Frame,
}
impl BSDF {
pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Ptr<BxDF>) -> Self {
Self {
bxdf,
shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)),
}
}
pub fn is_valid(&self) -> bool {
!self.bxdf.is_null()
}
pub fn flags(&self) -> BxDFFlags {
if self.bxdf.is_null() {
// Either this, or transmissive for seethrough
return BxDFFlags::empty();
}
self.bxdf.flags()
}
pub fn render_to_local(&self, v: Vector3f) -> Vector3f {
self.shading_frame.to_local(v)
}
pub fn local_to_render(&self, v: Vector3f) -> Vector3f {
self.shading_frame.from_local(v)
}
pub fn f(
&self,
wo_render: Vector3f,
wi_render: Vector3f,
mode: TransportMode,
) -> Option<SampledSpectrum> {
if self.bxdf.is_null() {
return None;
}
let wi = self.render_to_local(wi_render);
let wo = self.render_to_local(wo_render);
if wo.z() == 0.0 || wi.z() == 0.0 {
return None;
}
Some(self.bxdf.f(wo, wi, mode))
}
pub fn sample_f(
&self,
wo_render: Vector3f,
u: Float,
u2: Point2f,
f_args: FArgs,
) -> Option<BSDFSample> {
let bxdf = self.bxdf.as_ref()?;
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;
}
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(&self, wo_render: Vector3f, wi_render: Vector3f, f_args: FArgs) -> Float {
if self.bxdf.is_null() {
return 0.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 || !self.bxdf.flags().contains(sample_flags) {
return 0.0;
}
self.bxdf.pdf(wo, wi, f_args)
}
pub fn rho_u(&self, u1: &[Point2f], uc: &[Float], u2: &[Point2f]) -> SampledSpectrum {
if self.bxdf.is_null() {
return SampledSpectrum::default();
}
self.bxdf.rho_u(u1, uc, u2)
}
pub fn rho_wo(&self, wo_render: Vector3f, uc: &[Float], u: &[Point2f]) -> SampledSpectrum {
if self.bxdf.is_null() {
return SampledSpectrum::default();
}
let wo = self.render_to_local(wo_render);
self.bxdf.rho_wo(wo, uc, u)
}
pub fn regularize(&mut self) {
if !self.bxdf.is_null() {
unsafe { self.bxdf.as_mut().regularize() }
}
}
}

View file

@ -1,11 +1,13 @@
use crate::core::bxdf::BSDF; use crate::core::bxdf::{BSDF, NormalizedFresnelBxDF};
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f}; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Point3fi, Vector3f};
use crate::core::interaction::{InteractionData, Shadinggeom, SurfaceInteraction}; use crate::core::interaction::{InteractionBase, ShadingGeom, SurfaceInteraction};
use crate::core::pbrt::{Float, PI}; use crate::core::shape::Shape;
use crate::shapes::Shape;
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum};
use crate::utils::ArenaPtr;
use crate::utils::math::{catmull_rom_weights, square}; use crate::utils::math::{catmull_rom_weights, square};
use crate::utils::sampling::sample_catmull_rom_2d; use crate::utils::sampling::sample_catmull_rom_2d;
use crate::utils::{Ptr, ptr::Slice};
use crate::{Float, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc; use std::sync::Arc;
@ -19,13 +21,13 @@ pub struct BSSRDFSample {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SubsurfaceInteraction { pub struct SubsurfaceInteraction {
pi: Point3fi, pub pi: Point3fi,
n: Normal3f, pub n: Normal3f,
ns: Normal3f, pub ns: Normal3f,
dpdu: Vector3f, pub dpdu: Vector3f,
dpdv: Vector3f, pub dpdv: Vector3f,
dpdus: Vector3f, pub dpdus: Vector3f,
dpdvs: Vector3f, pub dpdvs: Vector3f,
} }
impl SubsurfaceInteraction { impl SubsurfaceInteraction {
@ -63,20 +65,12 @@ impl From<SurfaceInteraction> for SubsurfaceInteraction {
impl From<&SubsurfaceInteraction> for SurfaceInteraction { impl From<&SubsurfaceInteraction> for SurfaceInteraction {
fn from(ssi: &SubsurfaceInteraction) -> SurfaceInteraction { fn from(ssi: &SubsurfaceInteraction) -> SurfaceInteraction {
SurfaceInteraction { SurfaceInteraction {
common: InteractionData { common: InteractionBase::new_minimal(ssi.pi, ssi.n),
pi: ssi.pi,
n: ssi.n,
wo: Vector3f::zero(),
time: 0.,
medium_interface: None,
medium: None,
},
uv: Point2f::zero(),
dpdu: ssi.dpdu, dpdu: ssi.dpdu,
dpdv: ssi.dpdv, dpdv: ssi.dpdv,
dndu: Normal3f::zero(), dndu: Normal3f::zero(),
dndv: Normal3f::zero(), dndv: Normal3f::zero(),
shading: Shadinggeom { shading: ShadingGeom {
n: ssi.ns, n: ssi.ns,
dpdu: ssi.dpdus, dpdu: ssi.dpdus,
dpdv: ssi.dpdvs, dpdv: ssi.dpdvs,
@ -84,70 +78,86 @@ impl From<&SubsurfaceInteraction> for SurfaceInteraction {
dndv: Normal3f::zero(), dndv: Normal3f::zero(),
}, },
face_index: 0, face_index: 0,
area_light: None, area_light: Ptr::null(),
material: None, material: Ptr::null(),
dpdx: Vector3f::zero(), dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(), dpdy: Vector3f::zero(),
dudx: 0., dudx: 0.,
dvdx: 0., dvdx: 0.,
dudy: 0., dudy: 0.,
dvdy: 0., dvdy: 0.,
shape: Shape::default().into(), shape: Ptr::from(&Shape::default()),
} }
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct BSSRDFTable { pub struct BSSRDFTable {
rho_samples: Vec<Float>, pub n_rho_samples: u32,
radius_samples: Vec<Float>, pub n_radius_samples: u32,
profile: Vec<Float>, pub rho_samples: Ptr<Float>,
rho_eff: Vec<Float>, pub radius_samples: Ptr<Float>,
profile_cdf: Vec<Float>, pub profile: Ptr<Float>,
pub rho_eff: Ptr<Float>,
pub profile_cdf: Ptr<Float>,
} }
impl BSSRDFTable { impl BSSRDFTable {
pub fn new(n_rho_samples: usize, n_radius_samples: usize) -> Self { pub fn get_rho(&self) -> &[Float] {
let rho_samples: Vec<Float> = Vec::with_capacity(n_rho_samples); unsafe { core::slice::from_raw_parts(self.rho_samples.0, self.n_rho_samples as usize) }
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); pub fn get_radius(&self) -> &[Float] {
let profile_cdf: Vec<Float> = Vec::with_capacity(n_radius_samples * n_rho_samples); unsafe {
Self { core::slice::from_raw_parts(self.radius_samples.0, self.n_radius_samples as usize)
rho_samples,
radius_samples,
profile,
rho_eff,
profile_cdf,
} }
} }
pub fn eval_profile(&self, rho_index: usize, radius_index: usize) -> Float { pub fn get_profile(&self) -> &[Float] {
assert!(rho_index < self.rho_samples.len()); let n_profile = (self.n_rho_samples * self.n_radius_samples) as usize;
assert!(radius_index < self.radius_samples.len()); unsafe { core::slice::from_raw_parts(self.profile.0, n_profile) }
self.profile[rho_index * self.radius_samples.len() + radius_index] }
pub fn get_cdf(&self) -> &[Float] {
let n_profile = (self.n_rho_samples * self.n_radius_samples) as usize;
unsafe { core::slice::from_raw_parts(self.profile_cdf.0, n_profile) }
}
pub fn eval_profile(&self, rho_index: u32, radius_index: u32) -> Float {
debug_assert!(rho_index < self.n_rho_samples);
debug_assert!(radius_index < self.n_radius_samples);
let idx = (rho_index * self.n_radius_samples + radius_index) as usize;
unsafe { *self.profile.0.add(idx) }
} }
} }
#[derive(Clone, Default, Debug)] #[repr(C)]
#[derive(Copy, Clone, Default, Debug)]
pub struct BSSRDFProbeSegment { pub struct BSSRDFProbeSegment {
pub p0: Point3f, pub p0: Point3f,
pub p1: Point3f, pub p1: Point3f,
} }
#[enum_dispatch] #[enum_dispatch]
pub trait BSSRDFTrait: Send + Sync + std::fmt::Debug { pub trait BSSRDFTrait {
fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>; fn sample_sp(&self, u1: Float, u2: Point2f) -> Option<BSSRDFProbeSegment>;
fn probe_intersection_to_sample(&self, si: &SubsurfaceInteraction) -> BSSRDFSample; fn probe_intersection_to_sample(
&self,
si: &SubsurfaceInteraction,
bxdf: NormalizedFresnelBxDF,
) -> BSSRDFSample;
} }
#[repr(C)]
#[enum_dispatch(BSSRDFTrait)] #[enum_dispatch(BSSRDFTrait)]
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub enum BSSRDF { pub enum BSSRDF {
Tabulated(TabulatedBSSRDF), Tabulated(TabulatedBSSRDF),
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TabulatedBSSRDF { pub struct TabulatedBSSRDF {
po: Point3f, po: Point3f,
wo: Vector3f, wo: Vector3f,
@ -155,7 +165,7 @@ pub struct TabulatedBSSRDF {
eta: Float, eta: Float,
sigma_t: SampledSpectrum, sigma_t: SampledSpectrum,
rho: SampledSpectrum, rho: SampledSpectrum,
table: Arc<BSSRDFTable>, table: Ptr<BSSRDFTable>,
} }
impl TabulatedBSSRDF { impl TabulatedBSSRDF {
@ -166,7 +176,7 @@ impl TabulatedBSSRDF {
eta: Float, eta: Float,
sigma_a: &SampledSpectrum, sigma_a: &SampledSpectrum,
sigma_s: &SampledSpectrum, sigma_s: &SampledSpectrum,
table: Arc<BSSRDFTable>, table: &BSSRDFTable,
) -> Self { ) -> Self {
let sigma_t = *sigma_a + *sigma_s; let sigma_t = *sigma_a + *sigma_s;
let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t); let rho = SampledSpectrum::safe_div(sigma_s, &sigma_t);
@ -175,9 +185,9 @@ impl TabulatedBSSRDF {
wo, wo,
ns, ns,
eta, eta,
table,
sigma_t, sigma_t,
rho, rho,
table: Ptr::from(table),
} }
} }
@ -187,16 +197,17 @@ impl TabulatedBSSRDF {
pub fn sr(&self, r: Float) -> SampledSpectrum { pub fn sr(&self, r: Float) -> SampledSpectrum {
let mut sr_spectrum = SampledSpectrum::new(0.); let mut sr_spectrum = SampledSpectrum::new(0.);
let rho_samples = self.table.get_rho();
let radius_samples = self.table.get_radius();
for i in 0..N_SPECTRUM_SAMPLES { for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i]; let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) = let (rho_offset, rho_weights) = match catmull_rom_weights(rho_samples, self.rho[i]) {
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
let (radius_offset, radius_weights) = let (radius_offset, radius_weights) =
match catmull_rom_weights(&self.table.radius_samples, r_optical) { match catmull_rom_weights(radius_samples, r_optical) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
@ -206,7 +217,10 @@ impl TabulatedBSSRDF {
for (k, radius_weight) in radius_weights.iter().enumerate() { for (k, radius_weight) in radius_weights.iter().enumerate() {
let weight = rho_weight * radius_weight; let weight = rho_weight * radius_weight;
if weight != 0. { if weight != 0. {
sr += weight * self.table.eval_profile(rho_offset + j, radius_offset + k); sr += weight
* self
.table
.eval_profile(rho_offset + j as u32, radius_offset + k as u32);
} }
} }
} }
@ -216,7 +230,7 @@ impl TabulatedBSSRDF {
sr_spectrum[i] = sr; sr_spectrum[i] = sr;
} }
sr_spectrum *= self.sigma_t * self.sigma_t; sr_spectrum *= square(self.sigma_t);
SampledSpectrum::clamp_zero(&sr_spectrum) SampledSpectrum::clamp_zero(&sr_spectrum)
} }
@ -224,29 +238,30 @@ impl TabulatedBSSRDF {
if self.sigma_t[0] == 0. { if self.sigma_t[0] == 0. {
return None; return None;
} }
let (ret, _, _) = sample_catmull_rom_2d(
&self.table.rho_samples, let rho_samples = self.table.get_rho();
&self.table.radius_samples, let radius_samples = self.table.get_radius();
&self.table.profile, let profile = self.table.get_profile();
&self.table.profile_cdf, let cdf = self.table.get_cdf();
self.rho[0],
u, let (ret, _, _) =
); sample_catmull_rom_2d(rho_samples, radius_samples, profile, cdf, self.rho[0], u);
Some(ret / self.sigma_t[0]) Some(ret / self.sigma_t[0])
} }
pub fn pdf_sr(&self, r: Float) -> SampledSpectrum { pub fn pdf_sr(&self, r: Float) -> SampledSpectrum {
let mut pdf = SampledSpectrum::new(0.); let mut pdf = SampledSpectrum::new(0.);
let rhoeff_samples = self.table.get_rho();
let radius_samples = self.table.get_radius();
for i in 0..N_SPECTRUM_SAMPLES { for i in 0..N_SPECTRUM_SAMPLES {
let r_optical = r * self.sigma_t[i]; let r_optical = r * self.sigma_t[i];
let (rho_offset, rho_weights) = let (rho_offset, rho_weights) = match catmull_rom_weights(rhoeff_samples, self.rho[i]) {
match catmull_rom_weights(&self.table.rho_samples, self.rho[i]) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
let (radius_offset, radius_weights) = let (radius_offset, radius_weights) =
match catmull_rom_weights(&self.table.radius_samples, r_optical) { match catmull_rom_weights(radius_samples, r_optical) {
Some(res) => res, Some(res) => res,
None => continue, None => continue,
}; };
@ -256,12 +271,14 @@ impl TabulatedBSSRDF {
for (j, rho_weight) in rho_weights.iter().enumerate() { for (j, rho_weight) in rho_weights.iter().enumerate() {
if *rho_weight != 0. { if *rho_weight != 0. {
// Update _rhoEff_ and _sr_ for wavelength // Update _rhoEff_ and _sr_ for wavelength
rho_eff += self.table.rho_eff[rho_offset + j] * rho_weight; rho_eff += rhoeff_samples[rho_offset as usize + j] * rho_weight;
// Fix: Use .iter().enumerate() for 'k' // Fix: Use .iter().enumerate() for 'k'
for (k, radius_weight) in radius_weights.iter().enumerate() { for (k, radius_weight) in radius_weights.iter().enumerate() {
if *radius_weight != 0. { if *radius_weight != 0. {
sr += self.table.eval_profile(rho_offset + j, radius_offset + k) sr += self
.table
.eval_profile(rho_offset + j as u32, radius_offset + k as u32)
* rho_weight * rho_weight
* radius_weight; * radius_weight;
} }
@ -321,7 +338,11 @@ impl BSSRDFTrait for TabulatedBSSRDF {
}) })
} }
fn probe_intersection_to_sample(&self, _si: &SubsurfaceInteraction) -> BSSRDFSample { fn probe_intersection_to_sample(
&self,
_si: &SubsurfaceInteraction,
_bxdf: NormalizedFresnelBxDF,
) -> BSSRDFSample {
todo!() todo!()
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,9 @@ use crate::core::medium::Medium;
use crate::core::options::RenderingCoordinateSystem; use crate::core::options::RenderingCoordinateSystem;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::images::ImageMetadata;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::lerp; use crate::utils::math::lerp;
use crate::utils::ptr::Ptr;
use crate::utils::transform::{AnimatedTransform, Transform}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
@ -109,22 +109,12 @@ pub struct CameraBase {
pub camera_transform: CameraTransform, pub camera_transform: CameraTransform,
pub shutter_open: Float, pub shutter_open: Float,
pub shutter_close: Float, pub shutter_close: Float,
pub film: *const Film,
pub medium: *const Medium,
pub min_pos_differential_x: Vector3f, pub min_pos_differential_x: Vector3f,
pub min_pos_differential_y: Vector3f, pub min_pos_differential_y: Vector3f,
pub min_dir_differential_x: Vector3f, pub min_dir_differential_x: Vector3f,
pub min_dir_differential_y: Vector3f, pub min_dir_differential_y: Vector3f,
} pub film: Ptr<Film>,
pub medium: Ptr<Medium>,
#[cfg(not(target_os = "cuda"))]
impl CameraBase {
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
let camera_from_world: Transform =
self.camera_transform.camera_from_world(self.shutter_open);
metadata.camera_from_world = Some(camera_from_world.get_matrix());
}
} }
#[enum_dispatch(CameraTrait)] #[enum_dispatch(CameraTrait)]
@ -139,19 +129,19 @@ pub enum Camera {
#[enum_dispatch] #[enum_dispatch]
pub trait CameraTrait { pub trait CameraTrait {
#[cfg(not(target_os = "cuda"))]
fn init_metadata(&self, metadata: &mut ImageMetadata);
fn base(&self) -> &CameraBase; fn base(&self) -> &CameraBase;
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>; fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
fn get_film(&self) -> &Film {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn get_film(&self) -> Result<&Film, String> { {
if self.film.is_null() { if self.base().film.is_null() {
return Err("Camera error: Film pointer is null.".to_string()); panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
} }
Ok(unsafe { &*self.film }) }
&*self.base().film
} }
fn sample_time(&self, u: Float) -> Float { fn sample_time(&self, u: Float) -> Float {
@ -210,7 +200,7 @@ pub trait CameraTrait {
} }
if rx_found && ry_found { if rx_found && ry_found {
central_cam_ray.ray.differential = Some(rd); central_cam_ray.ray.differential = rd;
} }
Some(central_cam_ray) Some(central_cam_ray)
@ -242,13 +232,13 @@ pub trait CameraTrait {
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x, Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x,
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x, Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x,
None, None,
None, &Ptr::default(),
); );
let y_ray = Ray::new( let y_ray = Ray::new(
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y, Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y,
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y, Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y,
None, None,
None, &Ptr::default(),
); );
let n_down = Vector3f::from(n_down_z); let n_down = Vector3f::from(n_down_z);
let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d); let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d);

View file

@ -282,6 +282,18 @@ impl RGB {
self.r.max(self.g).max(self.b) self.r.max(self.g).max(self.b)
} }
pub fn min_component_value(&self) -> Float {
self.r.min(self.g).min(self.b)
}
pub fn min_component_index(&self) -> usize {
if self.r < self.g {
if self.r < self.b { 0 } else { 2 }
} else {
if self.g < self.b { 1 } else { 2 }
}
}
pub fn max_component_index(&self) -> usize { pub fn max_component_index(&self) -> usize {
if self.r > self.g { if self.r > self.g {
if self.r > self.b { 0 } else { 2 } if self.r > self.b { 0 } else { 2 }
@ -290,8 +302,16 @@ impl RGB {
} }
} }
pub fn clamp_zero(rgb: Self) -> Self { pub fn clamp(&self, min: Float, max: Float) -> Self {
RGB::new(rgb.r.max(0.), rgb.b.max(0.), rgb.g.max(0.)) RGB::new(
clamp(self.r, min, max),
clamp(self.g, min, max),
clamp(self.b, min, max),
)
}
pub fn clamp_zero(&self) -> Self {
RGB::new(self.r.max(0.), self.b.max(0.), self.g.max(0.))
} }
} }
@ -549,10 +569,23 @@ impl RGBSigmoidPolynomial {
} }
#[enum_dispatch] #[enum_dispatch]
pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display { pub trait ColorEncodingTrait: 'static + Send + Sync {
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]); fn from_linear(&self, vin: &[Float], vout: &mut [u8]);
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]); fn to_linear(&self, vin: &[u8], vout: &mut [Float]);
fn to_float_linear(&self, v: Float) -> Float; fn to_float_linear(&self, v: Float) -> Float;
fn from_linear_scalar(&self, v: Float) -> u8 {
let mut out = [0u8; 1];
self.from_linear(&[v], &mut out);
out[0]
}
fn to_linear_scalar(&self, v: u8) -> Float {
let mut out = [0.0; 1];
self.to_linear(&[v], &mut out);
out[0]
}
fn type_id(&self) -> TypeId { fn type_id(&self) -> TypeId {
TypeId::of::<Self>() TypeId::of::<Self>()
} }
@ -576,19 +609,29 @@ impl fmt::Display for ColorEncoding {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct LinearEncoding; pub struct LinearEncoding;
impl ColorEncodingTrait for LinearEncoding { impl ColorEncodingTrait for LinearEncoding {
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) { fn from_linear(&self, vin: &[Float], vout: &mut [u8]) {
for (i, &v) in vin.iter().enumerate() { for (i, &v) in vin.iter().enumerate() {
vout[i] = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8; vout[i] = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
} }
} }
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) {
fn to_linear(&self, vin: &[u8], vout: &mut [Float]) {
for (i, &v) in vin.iter().enumerate() { for (i, &v) in vin.iter().enumerate() {
vout[i] = v as Float / 255.0; vout[i] = v as Float / 255.0;
} }
} }
fn to_float_linear(&self, v: Float) -> Float { fn to_float_linear(&self, v: Float) -> Float {
v v
} }
fn from_linear_scalar(&self, v: Float) -> u8 {
(v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
}
fn to_linear_scalar(&self, v: u8) -> Float {
v as Float / 255.0
}
} }
impl fmt::Display for LinearEncoding { impl fmt::Display for LinearEncoding {
@ -601,7 +644,7 @@ impl fmt::Display for LinearEncoding {
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct SRGBEncoding; pub struct SRGBEncoding;
impl ColorEncodingTrait for SRGBEncoding { impl ColorEncodingTrait for SRGBEncoding {
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) { fn from_linear(&self, vin: &[Float], vout: &mut [u8]) {
for (i, &v_linear) in vin.iter().enumerate() { for (i, &v_linear) in vin.iter().enumerate() {
let v = v_linear.clamp(0.0, 1.0); let v = v_linear.clamp(0.0, 1.0);
let v_encoded = if v <= 0.0031308 { let v_encoded = if v <= 0.0031308 {
@ -613,12 +656,33 @@ impl ColorEncodingTrait for SRGBEncoding {
} }
} }
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) { fn to_linear(&self, vin: &[u8], vout: &mut [Float]) {
for (i, &v) in vin.iter().enumerate() { for (i, &v) in vin.iter().enumerate() {
vout[i] = SRGB_TO_LINEAR_LUT[v as usize]; vout[i] = SRGB_TO_LINEAR_LUT[v as usize];
} }
} }
fn from_linear_scalar(&self, v: Float) -> u8 {
let v_clamped = v.clamp(0.0, 1.0);
let v_encoded = if v_clamped <= 0.0031308 {
v_clamped * 12.92
} else {
1.055 * v_clamped.powf(1.0 / 2.4) - 0.055
};
(v_encoded * 255.0 + 0.5) as u8
}
fn to_linear_scalar(&self, v: u8) -> Float {
// Normalize 0-255 to 0.0-1.0 first
let v_float = v as Float / 255.0;
// Apply sRGB -> Linear math
if v_float <= 0.04045 {
v_float / 12.92
} else {
((v_float + 0.055) / 1.055).powf(2.4)
}
}
fn to_float_linear(&self, v: Float) -> Float { fn to_float_linear(&self, v: Float) -> Float {
let v = v.clamp(0.0, 1.0); let v = v.clamp(0.0, 1.0);
if v <= 0.04045 { if v <= 0.04045 {
@ -1015,3 +1079,28 @@ impl RGBToSpectrumTable {
} }
} }
} }
const LMS_FROM_XYZ: SquareMatrix3f = SquareMatrix::new([
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
]);
const XYZ_FROM_LMS: SquareMatrix3f = SquareMatrix::new([
[0.986993, -0.147054, 0.159963],
[0.432305, 0.51836, 0.0492912],
[-0.00852866, 0.0400428, 0.968487],
]);
pub fn white_balance(src_white: Point2f, target_white: Point2f) -> SquareMatrix3f {
let src_xyz = XYZ::from_xyy(src_white, None);
let dst_xyz = XYZ::from_xyy(target_white, None);
let src_lms = LMS_FROM_XYZ * src_xyz;
let dst_lms = LMS_FROM_XYZ * dst_xyz;
let lms_correct = SquareMatrix3f::diag(&[
dst_lms[0] / src_lms[0],
dst_lms[1] / src_lms[1],
dst_lms[2] / src_lms[2],
]);
XYZ_FROM_LMS * lms_correct * LMS_FROM_XYZ
}

View file

@ -1,23 +1,23 @@
use crate::core::camera::CameraTransform; use crate::core::camera::CameraTransform;
use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance}; use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
use crate::core::filter::Filter; use crate::core::filter::{Filter, FilterTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
Vector2i, Vector3f, Vector2i, Vector3f,
}; };
use crate::core::image::{Image, PixelFormat};
use crate::core::interaction::SurfaceInteraction; use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra}; use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
use crate::spectra::{ use crate::spectra::{
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace, PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
get_named_spectrum,
}; };
use crate::utils::AtomicFloat; use crate::utils::AtomicFloat;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use crate::utils::math::linear_least_squares; use crate::utils::math::linear_least_squares;
use crate::utils::math::{SquareMatrix, wrap_equal_area_square}; use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
use crate::utils::ptr::Ptr;
use crate::utils::sampling::VarianceEstimator; use crate::utils::sampling::VarianceEstimator;
use crate::utils::transform::AnimatedTransform; use crate::utils::transform::AnimatedTransform;
@ -33,52 +33,52 @@ pub struct RGBFilm {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
pub struct RGBPixel { pub struct RGBPixel {
rgb_sum: [AtomicFloat; 3], rgb_sum: [AtomicFloat; 3],
weight_sum: AtomicFloat, weight_sum: AtomicFloat,
rgb_splat: [AtomicFloat; 3], rgb_splat: [AtomicFloat; 3],
} }
#[cfg(not(target_os = "cuda"))] // #[cfg(not(target_os = "cuda"))]
impl RGBFilm { // impl RGBFilm {
pub fn new( // pub fn new(
base: FilmBase, // base: FilmBase,
colorspace: &RGBColorSpace, // colorspace: &RGBColorSpace,
max_component_value: Float, // max_component_value: Float,
write_fp16: bool, // write_fp16: bool,
) -> Self { // ) -> Self {
let sensor_ptr = base.sensor; // let sensor_ptr = base.sensor;
if sensor_ptr.is_null() { // if sensor_ptr.is_null() {
panic!("Film must have a sensor"); // panic!("Film must have a sensor");
} // }
let sensor = unsafe { &*sensor_ptr }; // let sensor = unsafe { &*sensor_ptr };
let filter_integral = base.filter.integral(); // let filter_integral = base.filter.integral();
let sensor_matrix = sensor.xyz_from_sensor_rgb; // let sensor_matrix = sensor.xyz_from_sensor_rgb;
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix; // let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
//
let width = base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x(); // let width = base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x();
let height = base.pixel_bounds.p_max.y() - base.pixel_bounds.p_min.y(); // let height = base.pixel_bounds.p_max.y() - base.pixel_bounds.p_min.y();
let count = (width * height) as usize; // let count = (width * height) as usize;
//
let mut pixel_vec = Vec::with_capacity(count); // let mut pixel_vec = Vec::with_capacity(count);
for _ in 0..count { // for _ in 0..count {
pixel_vec.push(RGBPixel::default()); // pixel_vec.push(RGBPixel::default());
} // }
//
let pixels_array = Array2D::new(base.pixel_bounds); // let pixels_array = Array2D::(base.pixel_bounds);
//
Self { // Self {
base, // base,
max_component_value, // max_component_value,
write_fp16, // write_fp16,
filter_integral, // filter_integral,
output_rgbf_from_sensor_rgb, // output_rgbf_from_sensor_rgb,
pixels: std::sync::Arc::new(pixels_array), // pixels: std::sync::Arc::new(pixels_array),
} // }
} // }
} // }
//
impl RGBFilm { impl RGBFilm {
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
@ -91,13 +91,13 @@ impl RGBFilm {
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
{ {
if self.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction." "FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
); );
} }
} }
unsafe { &*self.sensor } unsafe { &*self.base.sensor }
} }
pub fn add_sample( pub fn add_sample(
@ -131,18 +131,19 @@ impl RGBFilm {
} }
let p_discrete = p + Vector2f::new(0.5, 0.5); let p_discrete = p + Vector2f::new(0.5, 0.5);
let radius = self.get_filter().radius(); let radius = self.base.filter.radius();
let splat_bounds = Bounds2i::from_points( let splat_bounds = Bounds2i::from_points(
(p_discrete - radius).floor(), (p_discrete - radius).floor(),
(p_discrete + radius).floor() + Vector2i::new(1, 1), (p_discrete + radius).floor() + Vector2i::new(1, 1),
); );
let splat_intersect = splat_bounds.union(self.pixel_bounds()); let splat_intersect = splat_bounds.union(self.base().pixel_bounds);
for pi in &splat_intersect { for pi in &splat_intersect {
let pi_f: Point2f = (*pi).into(); let pi_f: Point2f = (*pi).into();
let wt = self let wt = self
.get_filter() .base()
.filter
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
if wt != 0. { if wt != 0. {
let pixel = &self.pixels[*pi]; let pixel = &self.pixels[*pi];
@ -154,7 +155,7 @@ impl RGBFilm {
} }
pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB { pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
let pixel = unsafe { &self.pixels.get(p.x(), p.y())[p] }; let pixel = unsafe { &self.pixels.get(p) };
let mut rgb = RGB::new( let mut rgb = RGB::new(
pixel.rgb_sum[0].load() as Float, pixel.rgb_sum[0].load() as Float,
pixel.rgb_sum[1].load() as Float, pixel.rgb_sum[1].load() as Float,
@ -180,8 +181,8 @@ impl RGBFilm {
} }
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let sensor = unsafe { self.get_sensor() }; let sensor = self.get_sensor();
let mut sensor_rgb = sensor.to_sensor_rgb(l, lambda); let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
} }
@ -190,7 +191,9 @@ impl RGBFilm {
} }
} }
#[derive(Debug, Clone, Copy, Default)] #[repr(C)]
#[derive(Debug, Default)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
struct GBufferPixel { struct GBufferPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub weight_sum: AtomicFloat, pub weight_sum: AtomicFloat,
@ -206,7 +209,9 @@ struct GBufferPixel {
pub rgb_variance: VarianceEstimator, pub rgb_variance: VarianceEstimator,
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Debug, Copy)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct GBufferFilm { pub struct GBufferFilm {
pub base: FilmBase, pub base: FilmBase,
output_from_render: AnimatedTransform, output_from_render: AnimatedTransform,
@ -265,13 +270,13 @@ impl GBufferFilm {
pub fn get_sensor(&self) -> &PixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
{ {
if self.sensor.is_null() { if self.base.sensor.is_null() {
panic!( panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction." "FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
); );
} }
} }
unsafe { &*self.sensor } unsafe { &*self.base.sensor }
} }
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
@ -283,18 +288,19 @@ impl GBufferFilm {
} }
let p_discrete = p + Vector2f::new(0.5, 0.5); let p_discrete = p + Vector2f::new(0.5, 0.5);
let radius = self.get_filter().radius(); let radius = self.base().filter.radius();
let splat_bounds = Bounds2i::from_points( let splat_bounds = Bounds2i::from_points(
(p_discrete - radius).floor(), (p_discrete - radius).floor(),
(p_discrete + radius).floor() + Vector2i::new(1, 1), (p_discrete + radius).floor() + Vector2i::new(1, 1),
); );
let splat_intersect = splat_bounds.union(self.pixel_bounds()); let splat_intersect = splat_bounds.union(self.base.pixel_bounds);
for pi in &splat_intersect { for pi in &splat_intersect {
let pi_f: Point2f = (*pi).into(); let pi_f: Point2f = (*pi).into();
let wt = self let wt = self
.get_filter() .base
.filter
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
if wt != 0. { if wt != 0. {
let pixel = &self.pixels[*pi]; let pixel = &self.pixels[*pi];
@ -306,13 +312,13 @@ impl GBufferFilm {
} }
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let sensor = unsafe { self.get_sensor() }; let sensor = self.get_sensor();
let sensor_rgb = sensor.to_sensor_rgb(l, lambda); let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
} }
pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB { pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
let pixel = unsafe { &self.pixels.get(p.x(), p.y()) }; let pixel = unsafe { &self.pixels.get(p) };
let mut rgb = RGB::new( let mut rgb = RGB::new(
pixel.rgb_sum[0].load() as Float, pixel.rgb_sum[0].load() as Float,
pixel.rgb_sum[1].load() as Float, pixel.rgb_sum[1].load() as Float,
@ -343,7 +349,8 @@ impl GBufferFilm {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct SpectralPixel { pub struct SpectralPixel {
pub rgb_sum: [AtomicFloat; 3], pub rgb_sum: [AtomicFloat; 3],
pub rgb_weigh_sum: AtomicFloat, pub rgb_weigh_sum: AtomicFloat,
@ -351,15 +358,9 @@ pub struct SpectralPixel {
pub bucket_offset: usize, pub bucket_offset: usize,
} }
pub struct SpectralPixelView<'a> {
pub metadata: &'a SpectralPixel,
pub bucket_sums: &'a [f64],
pub weight_sums: &'a [f64],
pub bucket_splats: &'a [AtomicFloat],
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Default)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct SpectralFilm { pub struct SpectralFilm {
pub base: FilmBase, pub base: FilmBase,
pub colorspace: RGBColorSpace, pub colorspace: RGBColorSpace,
@ -371,59 +372,9 @@ pub struct SpectralFilm {
pub filter_integral: Float, pub filter_integral: Float,
pub pixels: Array2D<SpectralPixel>, pub pixels: Array2D<SpectralPixel>,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub bucket_sums: Vec<f64>, pub bucket_sums: *mut f64,
pub weight_sums: Vec<f64>, pub weight_sums: *mut f64,
pub bucket_splats: Vec<AtomicFloat>, pub bucket_splats: *mut AtomicFloat,
}
#[cfg(not(target_os = "cuda"))]
impl SpectralFilm {
pub fn new(
base: &FilmBase,
lambda_min: Float,
lambda_max: Float,
n_buckets: usize,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
assert!(!base.pixel_bounds.is_empty());
let sensor_ptr = base.sensor;
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = unsafe { &*sensor_ptr };
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb;
let n_pixels = base.pixel_bounds.area() as usize;
let total_bucket_count = n_pixels * n_buckets;
let bucket_sums = vec![0.0; total_bucket_count];
let weight_sums = vec![0.0; total_bucket_count];
let filter_integral = base.filter.integral();
let bucket_splats: Vec<AtomicFloat> = (0..total_bucket_count)
.map(|_| AtomicFloat::new(0.0))
.collect();
let mut pixels = Array2D::<SpectralPixel>::new(base.pixel_bounds);
for i in 0..n_pixels {
pixels.get_linear_mut(i).bucket_offset = i * n_buckets;
}
Self {
base: base.clone(),
lambda_min,
lambda_max,
n_buckets,
pixels,
bucket_sums,
weight_sums,
bucket_splats,
colorspace: colorspace.clone(),
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
}
}
} }
impl SpectralFilm { impl SpectralFilm {
@ -438,19 +389,6 @@ impl SpectralFilm {
fn uses_visible_surface(&self) -> bool { fn uses_visible_surface(&self) -> bool {
true true
} }
pub fn get_pixel_view(&self, p: Point2i) -> SpectralPixelView {
let metadata = unsafe { &self.pixels.get(p.x(), p.y()) };
let start = metadata.bucket_offset;
let end = start + self.n_buckets;
SpectralPixelView {
metadata,
bucket_sums: &self.bucket_sums[start..end],
weight_sums: &self.weight_sums[start..end],
bucket_splats: &self.bucket_splats[start..end],
}
}
} }
#[repr(C)] #[repr(C)]
@ -471,14 +409,15 @@ impl PixelSensor {
g: Spectrum, g: Spectrum,
b: Spectrum, b: Spectrum,
output_colorspace: RGBColorSpace, output_colorspace: RGBColorSpace,
sensor_illum: Option<std::sync::Arc<Spectrum>>, sensor_illum: &Spectrum,
imaging_ratio: Float, imaging_ratio: Float,
spectra: *const StandardSpectra,
swatches: &[Spectrum; 24], swatches: &[Spectrum; 24],
) -> Result<Self, Box<dyn Error>> { ) -> Self {
// As seen in usages of this constructos, sensor_illum can be null // As seen in usages of this constructos, sensor_illum can be null
// Going with the colorspace's own illuminant, but this might not be the right choice // Going with the colorspace's own illuminant, but this might not be the right choice
// TODO: Test this // TODO: Test this
let illum: &Spectrum = match &sensor_illum { let illum: &Spectrum = match sensor_illum {
Some(arc_illum) => &**arc_illum, Some(arc_illum) => &**arc_illum,
None => &output_colorspace.illuminant, None => &output_colorspace.illuminant,
}; };
@ -505,15 +444,15 @@ impl PixelSensor {
let mut xyz_output = [[0.; 3]; Self::N_SWATCH_REFLECTANCES]; let mut xyz_output = [[0.; 3]; Self::N_SWATCH_REFLECTANCES];
let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone())); let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
let sensor_white_y = illum.inner_product(cie_y()); let sensor_white_y = illum.inner_product(spectra.y);
for i in 0..Self::N_SWATCH_REFLECTANCES { for i in 0..Self::N_SWATCH_REFLECTANCES {
let s = swatches[i].clone(); let s = swatches[i].clone();
let xyz = Self::project_reflectance::<XYZ>( let xyz = Self::project_reflectance::<XYZ>(
&s, &s,
&output_colorspace.illuminant, &output_colorspace.illuminant,
cie_x(), spectra.x,
cie_y(), spectra.y,
cie_z(), spectra.z,
) * (sensor_white_y / sensor_white_g); ) * (sensor_white_y / sensor_white_g);
for c in 0..3 { for c in 0..3 {
xyz_output[i][c] = xyz[c]; xyz_output[i][c] = xyz[c];
@ -533,16 +472,17 @@ impl PixelSensor {
pub fn new_with_white_balance( pub fn new_with_white_balance(
output_colorspace: &RGBColorSpace, output_colorspace: &RGBColorSpace,
sensor_illum: Option<std::sync::Arc<Spectrum>>, sensor_illum: Ptr<Spectrum>,
imaging_ratio: Float, imaging_ratio: Float,
spectra: *const StandardSpectra,
) -> Self { ) -> Self {
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x()); let r_bar = DenselySampledSpectrum::from_spectrum(spectra.x);
let g_bar = DenselySampledSpectrum::from_spectrum(cie_y()); let g_bar = DenselySampledSpectrum::from_spectrum(spectra.y);
let b_bar = DenselySampledSpectrum::from_spectrum(cie_z()); let b_bar = DenselySampledSpectrum::from_spectrum(spectra.z);
let xyz_from_sensor_rgb: SquareMatrix<Float, 3>; let xyz_from_sensor_rgb: SquareMatrix<Float, 3>;
if let Some(illum) = sensor_illum { if let Some(illum) = sensor_illum {
let source_white = illum.to_xyz().xy(); let source_white = illum.to_xyz(spectra).xy();
let target_white = output_colorspace.w; let target_white = output_colorspace.w;
xyz_from_sensor_rgb = white_balance(source_white, target_white); xyz_from_sensor_rgb = white_balance(source_white, target_white);
} else { } else {
@ -633,7 +573,7 @@ impl VisibleSurface {
} }
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct FilmBase { pub struct FilmBase {
pub full_resolution: Point2i, pub full_resolution: Point2i,
pub pixel_bounds: Bounds2i, pub pixel_bounds: Bounds2i,
@ -643,7 +583,8 @@ pub struct FilmBase {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug)]
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub enum Film { pub enum Film {
RGB(RGBFilm), RGB(RGBFilm),
GBuffer(GBufferFilm), GBuffer(GBufferFilm),

View file

@ -11,7 +11,8 @@ pub struct FilterSample {
pub weight: Float, pub weight: Float,
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct FilterSampler { pub struct FilterSampler {
pub domain: Bounds2f, pub domain: Bounds2f,
pub distrib: PiecewiseConstant2D, pub distrib: PiecewiseConstant2D,
@ -53,11 +54,12 @@ impl FilterSampler {
return FilterSample { p, weight: 0.0 }; return FilterSample { p, weight: 0.0 };
} }
let weight = *self.f.get_linear(pi.x() as usize + self.f.x_size()) / pdf; let weight = *self.f.get_linear(pi.x() as u32 + self.f.x_size()) / pdf;
FilterSample { p, weight } FilterSample { p, weight }
} }
} }
#[enum_dispatch]
pub trait FilterTrait { pub trait FilterTrait {
fn radius(&self) -> Vector2f; fn radius(&self) -> Vector2f;
fn evaluate(&self, p: Point2f) -> Float; fn evaluate(&self, p: Point2f) -> Float;
@ -67,7 +69,7 @@ pub trait FilterTrait {
#[repr(C)] #[repr(C)]
#[enum_dispatch(FilterTrait)] #[enum_dispatch(FilterTrait)]
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Filter { pub enum Filter {
Box(BoxFilter), Box(BoxFilter),
Gaussian(GaussianFilter), Gaussian(GaussianFilter),

View file

@ -7,8 +7,9 @@ pub mod traits;
pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i}; pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
pub use self::cone::DirectionCone; pub use self::cone::DirectionCone;
pub use self::primitives::{ pub use self::primitives::{
Frame, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi, Point3i, Frame, MulAdd, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi,
Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi, Vector3i, Point3i, Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi,
Vector3i,
}; };
pub use self::ray::{Ray, RayDifferential}; pub use self::ray::{Ray, RayDifferential};
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike}; pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};

View file

@ -9,6 +9,19 @@ use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
pub trait MulAdd<M = Self, A = Self> {
type Output;
fn mul_add(self, multiplier: M, addend: A) -> Self::Output;
}
impl MulAdd<Float, Float> for Float {
type Output = Float;
#[inline(always)]
fn mul_add(self, multiplier: Float, addend: Float) -> Self::Output {
self.mul_add(multiplier, addend)
}
}
// N-dimensional displacement // N-dimensional displacement
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -260,6 +273,27 @@ macro_rules! impl_op_assign {
}; };
} }
#[macro_export]
macro_rules! impl_mul_add {
($Struct:ident) => {
impl<T, const N: usize> MulAdd<T, $Struct<T, N>> for $Struct<T, N>
where
T: MulAdd<T, T, Output = T> + Copy,
{
type Output = $Struct<T, N>;
#[inline(always)]
fn mul_add(self, multiplier: T, addend: $Struct<T, N>) -> Self::Output {
let mut result = self.0;
for i in 0..N {
result[i] = self.0[i].mul_add(multiplier, addend.0[i]);
}
Self(result)
}
}
};
}
#[macro_export] #[macro_export]
macro_rules! impl_float_vector_ops { macro_rules! impl_float_vector_ops {
($Struct:ident) => { ($Struct:ident) => {
@ -381,6 +415,10 @@ impl_accessors!(Vector);
impl_accessors!(Point); impl_accessors!(Point);
impl_accessors!(Normal); impl_accessors!(Normal);
impl_mul_add!(Vector);
impl_mul_add!(Point);
impl_mul_add!(Normal);
// Convert from tuple of Floats, for parsing issues // Convert from tuple of Floats, for parsing issues
impl_tuple_conversions!(Vector); impl_tuple_conversions!(Vector);
impl_tuple_conversions!(Point); impl_tuple_conversions!(Point);
@ -778,7 +816,8 @@ impl<T> Normal3<T>
where where
T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt, T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt,
{ {
pub fn face_forward(self, v: Vector3<T>) -> Self { pub fn face_forward(self, v: impl Into<Vector3<T>>) -> Self {
let v: Vector3<T> = v.into();
if Vector3::<T>::from(self).dot(v) < T::zero() { if Vector3::<T>::from(self).dot(v) < T::zero() {
-self -self
} else { } else {
@ -787,8 +826,8 @@ where
} }
} }
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OctahedralVector { pub struct OctahedralVector {
x: u16, x: u16,
y: u16, y: u16,
@ -850,6 +889,7 @@ impl From<OctahedralVector> for Vector3f {
} }
} }
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialEq)] #[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Frame { pub struct Frame {
pub x: Vector3f, pub x: Vector3f,

View file

@ -2,16 +2,18 @@ use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::math::{next_float_down, next_float_up}; use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::ptr::Ptr;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Ray { pub struct Ray {
pub o: Point3f, pub o: Point3f,
pub d: Vector3f, pub d: Vector3f,
pub medium: *const Medium,
pub time: Float, pub time: Float,
pub medium: Ptr<Medium>,
// We do this instead of creating a trait for Rayable or some gnarly thing like that // We do this instead of creating a trait for Rayable or some gnarly thing like that
pub differential: *const RayDifferential, pub has_differentials: bool,
pub differential: RayDifferential,
} }
impl Default for Ray { impl Default for Ray {
@ -19,20 +21,21 @@ impl Default for Ray {
Self { Self {
o: Point3f::new(0.0, 0.0, 0.0), o: Point3f::new(0.0, 0.0, 0.0),
d: Vector3f::new(0.0, 0.0, 0.0), d: Vector3f::new(0.0, 0.0, 0.0),
medium: None, medium: Ptr::null(),
time: 0.0, time: 0.0,
differential: None, has_differentials: false,
differential: RayDifferential::default(),
} }
} }
} }
impl Ray { impl Ray {
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: *const Medium) -> Self { pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: &Medium) -> Self {
Self { Self {
o, o,
d, d,
time: time.unwrap_or_else(|| Self::default().time), time: time.unwrap_or_else(|| Self::default().time),
medium, medium: Ptr::from(medium),
..Self::default() ..Self::default()
} }
} }
@ -68,8 +71,9 @@ impl Ray {
o: origin, o: origin,
d, d,
time, time,
medium: None, medium: Ptr::null(),
differential: None, has_differentials: false,
differential: RayDifferential::default(),
} }
} }
@ -95,13 +99,15 @@ impl Ray {
o: pf, o: pf,
d, d,
time, time,
medium: None, medium: Ptr::null(),
differential: None, has_differentials: false,
differential: RayDifferential::default(),
} }
} }
pub fn scale_differentials(&mut self, s: Float) { pub fn scale_differentials(&mut self, s: Float) {
if let Some(differential) = &mut self.differential { if self.has_differentials {
let differential = &mut self.differential;
differential.rx_origin = self.o + (differential.rx_origin - self.o) * s; differential.rx_origin = self.o + (differential.rx_origin - self.o) * s;
differential.ry_origin = self.o + (differential.ry_origin - self.o) * s; differential.ry_origin = self.o + (differential.ry_origin - self.o) * s;
differential.rx_direction = self.d + (differential.rx_direction - self.d) * s; differential.rx_direction = self.d + (differential.rx_direction - self.d) * s;

178
shared/src/core/image.rs Normal file
View file

@ -0,0 +1,178 @@
use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::core::pbrt::Float;
use crate::utils::containers::Array2D;
use crate::utils::math::{f16_to_f32, lerp, square};
use core::hash;
use half::f16;
use smallvec::{SmallVec, smallvec};
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WrapMode {
Black,
Clamp,
Repeat,
OctahedralSphere,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WrapMode2D {
pub uv: [WrapMode; 2],
}
impl From<WrapMode> for WrapMode2D {
fn from(w: WrapMode) -> Self {
Self { uv: [w, w] }
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PixelFormat {
U8,
F16,
F32,
}
impl PixelFormat {
pub fn is_8bit(&self) -> bool {
matches!(self, PixelFormat::U8)
}
pub fn is_16bit(&self) -> bool {
matches!(self, PixelFormat::F16)
}
pub fn is_32bit(&self) -> bool {
matches!(self, PixelFormat::F32)
}
pub fn texel_bytes(&self) -> usize {
match self {
PixelFormat::U8 => 1,
PixelFormat::F16 => 2,
PixelFormat::F32 => 4,
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum Pixels {
U8(*const u8),
F16(*const u16),
F32(*const f32),
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Image {
pub format: PixelFormat,
pub pixels: Pixels,
pub encoding: ColorEncoding,
pub resolution: Point2i,
pub n_channels: i32,
}
impl Image {
pub fn resolution(&self) -> Point2i {
self.resolution
}
pub fn is_valid(&self) -> bool {
self.resolution.x() > 0. && self.resolution.y() > 0.
}
pub fn format(&self) -> PixelFormat {
self.format
}
pub fn n_channels(&self) -> i32 {
self.n_channels
}
pub fn pixel_offset(&self, p: Point2i) -> u32 {
let width = self.resolution.x() as u32;
let idx = p.y() as u32 * width + p.x() as u32;
idx * (self.n_channels as u32)
}
pub fn get_channel_with_wrap(&self, p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
if !self.remap_pixel_coords(&mut p, wrap_mode) {
return 0.;
}
let offset = self.pixel_offset(p) + c;
unsafe {
match self.pixels {
Pixels::U8(ptr) => {
let raw_u8 = *ptr.add(offset);
self.encoding.to_linear_scalar(raw_u8)
}
Pixels::F16(ptr) => {
let half_bits = *ptr.add(offset);
f16_to_f32(f16::from_bits(half_bits))
}
Pixels::F32(ptr) => *ptr.add(offset),
}
}
}
pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool {
for i in 0..2 {
if p[i] >= 0 && p[i] < self.resolution[i] {
continue;
}
match wrap_mode.uv[i] {
WrapMode::Black => return false,
WrapMode::Clamp => p[i] = p[i].clamp(0, self.resolution[i] - 1),
WrapMode::Repeat => p[i] = p[i].rem_euclid(self.resolution[i]),
WrapMode::OctahedralSphere => {
p[i] = p[i].clamp(0, self.resolution[i] - 1);
}
}
}
true
}
pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float {
self.bilerp_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float {
let x = p.x() * self.resolution.x() as Float - 0.5;
let y = p.y() * self.resolution.y() as Float - 0.5;
let xi = x.floor() as i32;
let yi = y.floor() as i32;
let dx = x - xi as Float;
let dy = y - yi as Float;
let v00 = self.get_channel_with_wrap(Point2i::new(xi, yi), c, wrap_mode);
let v10 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi), c, wrap_mode);
let v01 = self.get_channel_with_wrap(Point2i::new(xi, yi + 1), c, wrap_mode);
let v11 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi + 1), c, wrap_mode);
lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11))
}
pub fn lookup_nearest_channel_with_wrap(
&self,
p: Point2f,
c: i32,
wrap_mode: WrapMode2D,
) -> Float {
let pi = Point2i::new(
p.x() as i32 * self.resolution.x(),
p.y() as i32 * self.resolution.y(),
);
self.get_channel_with_wrap(pi, c, wrap_mode)
}
pub fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float {
self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
}

View file

@ -1,41 +1,92 @@
use crate::Float;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF}; use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
use crate::core::camera::Camera; use crate::core::camera::{Camera, CameraTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
}; };
use crate::core::light::Light; use crate::core::image::Image;
use crate::core::light::{Light, LightTrait};
use crate::core::material::{ use crate::core::material::{
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
}; };
use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
use crate::core::options::get_options; use crate::core::options::get_options;
use crate::core::pbrt::Float;
use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::sampler::{Sampler, SamplerTrait};
use crate::core::shape::Shape;
use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator}; use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
use crate::images::Image;
use crate::shapes::Shape;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::{clamp, difference_of_products, square}; use crate::utils::math::{clamp, difference_of_products, square};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::any::Any; use std::any::Any;
use std::default;
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone, Debug)] #[derive(Default, Copy, Clone, Debug)]
pub struct InteractionData { pub struct InteractionBase {
pub pi: Point3fi, pub pi: Point3fi,
pub n: Normal3f, pub n: Normal3f,
pub time: Float, pub time: Float,
pub wo: Vector3f, pub wo: Vector3f,
pub uv: Point2f,
pub medium_interface: MediumInterface, pub medium_interface: MediumInterface,
pub medium: *const Medium, pub medium: Ptr<Medium>,
}
impl InteractionBase {
pub fn new_surface_geom(
pi: Point3fi,
n: Normal3f,
uv: Point2f,
wo: Vector3f,
time: Float,
) -> Self {
Self {
pi,
n,
uv,
wo: wo.normalize(),
time,
medium_interface: MediumInterface::default(),
medium: Ptr::null(),
}
}
pub fn new_medium(p: Point3f, wo: Vector3f, time: Float, medium: Ptr<Medium>) -> Self {
Self {
pi: Point3fi::new_from_point(p),
n: Normal3f::zero(),
uv: Point2f::default(),
wo: wo.normalize(),
time,
medium_interface: MediumInterface::default(),
medium,
}
}
pub fn new_minimal(pi: Point3fi, n: Normal3f) -> Self {
Self {
pi,
n,
..Default::default()
}
}
pub fn new_boundary(p: Point3f, time: Float, medium_interface: MediumInterface) -> Self {
Self {
pi: Point3fi::new_from_point(p),
time,
medium_interface,
..Default::default()
}
}
} }
#[enum_dispatch] #[enum_dispatch]
pub trait InteractionTrait: Send + Sync + std::fmt::Debug { pub trait InteractionTrait {
fn get_common(&self) -> &InteractionData; fn get_common(&self) -> &InteractionBase;
fn get_common_mut(&mut self) -> &mut InteractionData; fn get_common_mut(&mut self) -> &mut InteractionBase;
fn p(&self) -> Point3f { fn p(&self) -> Point3f {
self.get_common().pi.into() self.get_common().pi.into()
@ -43,6 +94,7 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
fn pi(&self) -> Point3fi { fn pi(&self) -> Point3fi {
self.get_common().pi self.get_common().pi
} }
fn time(&self) -> Float { fn time(&self) -> Float {
self.get_common().time self.get_common().time
} }
@ -62,13 +114,13 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
false false
} }
fn get_medium(&self, w: Vector3f) -> *const Medium { fn get_medium(&self, w: Vector3f) -> Ptr<Medium> {
let data = self.get_common(); let data = self.get_common();
if let Some(mi) = &data.medium_interface { if !data.medium_interface.inside.is_null() || !data.medium_interface.outside.is_null() {
if w.dot(data.n.into()) > 0.0 { if w.dot(data.n.into()) > 0.0 {
mi.outside data.medium_interface.outside
} else { } else {
mi.inside data.medium_interface.inside
} }
} else { } else {
data.medium data.medium
@ -90,11 +142,10 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
ray ray
} }
fn spawn_ray_to_interaction(&self, other: InteractionData) -> Ray { fn spawn_ray_to_interaction(&self, other: InteractionBase) -> Ray {
let data = self.get_common(); let data = self.get_common();
let mut ray = let mut ray = Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other.pi, &other.n);
Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n);
ray.medium = self.get_medium(ray.d); ray.medium = self.get_medium(ray.d);
ray ray
} }
@ -118,57 +169,40 @@ pub enum Interaction {
} }
impl Interaction { impl Interaction {
pub fn set_medium_interface(&mut self, mi: Option<MediumInterface>) { pub fn set_medium_interface(&mut self, mi: MediumInterface) {
match self { match self {
Interaction::Surface(si) => si.common.medium_interface = mi, Interaction::Surface(si) => si.common.medium_interface = mi,
Interaction::Simple(si) => si.common.medium_interface = mi,
Interaction::Medium(_) => {} // Medium interactions don't usually sit on boundaries Interaction::Medium(_) => {} // Medium interactions don't usually sit on boundaries
Interaction::Simple(si) => si.common.medium_interface = mi,
} }
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Default, Clone, Copy)]
pub struct SimpleInteraction { pub struct SimpleInteraction {
pub common: InteractionData, pub common: InteractionBase,
} }
impl SimpleInteraction { impl SimpleInteraction {
pub fn new(pi: Point3fi, time: Float, medium_interface: Option<MediumInterface>) -> Self { pub fn new(common: InteractionBase) -> Self {
Self { Self { common }
common: InteractionData {
pi,
time,
medium_interface,
n: Normal3f::default(),
wo: Vector3f::default(),
medium: None,
},
}
}
pub fn new_interface(p: Point3f, medium_interface: Option<MediumInterface>) -> Self {
Self {
common: InteractionData {
pi: Point3fi::new_from_point(p),
n: Normal3f::zero(),
wo: Vector3f::zero(),
time: 0.0,
medium: None,
medium_interface,
},
}
} }
} }
impl InteractionTrait for SimpleInteraction { impl InteractionTrait for SimpleInteraction {
fn get_common(&self) -> &InteractionData { fn get_common(&self) -> &InteractionBase {
&self.common &self.common
} }
fn get_common_mut(&mut self) -> &mut InteractionBase {
fn get_common_mut(&mut self) -> &mut InteractionData {
&mut self.common &mut self.common
} }
fn is_surface_interaction(&self) -> bool {
false
}
fn is_medium_interaction(&self) -> bool {
false
}
} }
#[repr(C)] #[repr(C)]
@ -184,17 +218,16 @@ pub struct ShadingGeom {
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct SurfaceInteraction { pub struct SurfaceInteraction {
pub common: InteractionData, pub common: InteractionBase,
pub uv: Point2f,
pub dpdu: Vector3f, pub dpdu: Vector3f,
pub dpdv: Vector3f, pub dpdv: Vector3f,
pub dndu: Normal3f, pub dndu: Normal3f,
pub dndv: Normal3f, pub dndv: Normal3f,
pub shading: ShadingGeom, pub shading: ShadingGeom,
pub face_index: u32, pub face_index: u32,
pub area_light: *const Light, pub area_light: Ptr<Light>,
pub material: *const Material, pub material: Ptr<Material>,
pub shape: *const Shape, pub shape: Ptr<Shape>,
pub dpdx: Vector3f, pub dpdx: Vector3f,
pub dpdy: Vector3f, pub dpdy: Vector3f,
pub dudx: Float, pub dudx: Float,
@ -208,15 +241,17 @@ unsafe impl Sync for SurfaceInteraction {}
impl SurfaceInteraction { impl SurfaceInteraction {
pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if let Some(area_light) = &self.area_light { if !self.area_light.is_null() {
area_light.l(self.p(), self.n(), self.uv, w, lambda) self.area_light
.l(self.p(), self.n(), self.common.uv, w, lambda)
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
} }
} }
pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) { pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) {
let computed = if let Some(diff) = &r.differential { let computed = if !r.differential.is_null() {
let diff = unsafe { &*r.differential };
let dot_rx = self.common.n.dot(diff.rx_direction.into()); let dot_rx = self.common.n.dot(diff.rx_direction.into());
let dot_ry = self.common.n.dot(diff.ry_direction.into()); let dot_ry = self.common.n.dot(diff.ry_direction.into());
@ -303,7 +338,8 @@ impl SurfaceInteraction {
let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d); let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d);
ray.o = new_ray.o; ray.o = new_ray.o;
// Skipping other variables, since they should not change when passing through surface // Skipping other variables, since they should not change when passing through surface
if let Some(diff) = &mut ray.differential { if !ray.differential.is_null() {
let diff = unsafe { &mut *ray.differential };
diff.rx_origin += diff.rx_direction * t; diff.rx_origin += diff.rx_direction * t;
diff.ry_origin += diff.ry_direction * t; diff.ry_origin += diff.ry_direction * t;
} }
@ -320,8 +356,8 @@ impl SurfaceInteraction {
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32); self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
let material = { let material = {
let root_mat = self.material.as_deref()?; let root_mat = self.material;
let mut active_mat: &Material = root_mat; let mut active_mat: &Material = *root_mat;
let tex_eval = UniversalTextureEvaluator; let tex_eval = UniversalTextureEvaluator;
while let Material::Mix(mix) = active_mat { while let Material::Mix(mix) = active_mat {
// We need a context to evaluate the 'amount' texture // We need a context to evaluate the 'amount' texture
@ -376,12 +412,12 @@ impl SurfaceInteraction {
fn compute_bump_geom( fn compute_bump_geom(
&mut self, &mut self,
tex_eval: &UniversalTextureEvaluator, tex_eval: &UniversalTextureEvaluator,
displacement: *const GPUFloatTexture, displacement: Ptr<GPUFloatTexture>,
normal_image: *const Image, normal_image: Ptr<Image>,
) { ) {
let ctx = NormalBumpEvalContext::from(&*self); let ctx = NormalBumpEvalContext::from(&*self);
let (dpdu, dpdv) = if let Some(disp) = displacement { let (dpdu, dpdv) = if !displacement.is_null() {
bump_map(tex_eval, &disp, &ctx) bump_map(tex_eval, &displacement, &ctx)
} else if let Some(map) = normal_image { } else if let Some(map) = normal_image {
normal_map(map.as_ref(), &ctx) normal_map(map.as_ref(), &ctx)
} else { } else {
@ -475,14 +511,14 @@ impl SurfaceInteraction {
|| Vector3f::from(new_diff_rx_origin).norm_squared() > threshold || Vector3f::from(new_diff_rx_origin).norm_squared() > threshold
|| Vector3f::from(new_diff_ry_origin).norm_squared() > threshold || Vector3f::from(new_diff_ry_origin).norm_squared() > threshold
{ {
rd.differential = None; rd.differential = RayDifferential::default();
} else { } else {
rd.differential = Some(RayDifferential { rd.differential = RayDifferential {
rx_origin: new_diff_rx_origin, rx_origin: new_diff_rx_origin,
ry_origin: new_diff_ry_origin, ry_origin: new_diff_ry_origin,
rx_direction: new_diff_rx_dir, rx_direction: new_diff_rx_dir,
ry_direction: new_diff_ry_dir, ry_direction: new_diff_ry_dir,
}); };
} }
} }
} }
@ -492,22 +528,21 @@ impl SurfaceInteraction {
} }
impl InteractionTrait for SurfaceInteraction { impl InteractionTrait for SurfaceInteraction {
fn get_common(&self) -> &InteractionData { fn get_common(&self) -> &InteractionBase {
&self.common &self.common
} }
fn get_common_mut(&mut self) -> &mut InteractionData { fn get_common_mut(&mut self) -> &mut InteractionBase {
&mut self.common &mut self.common
} }
fn get_medium(&self, w: Vector3f) -> *const Medium { fn get_medium(&self, w: Vector3f) -> Ptr<Medium> {
self.common.medium_interface.as_ref().and_then(|interface| { let interface = self.common.medium_interface;
if self.n().dot(w.into()) > 0.0 { if self.n().dot(w.into()) > 0.0 {
interface.outside interface.outside
} else { } else {
interface.inside interface.inside
} }
})
} }
fn is_surface_interaction(&self) -> bool { fn is_surface_interaction(&self) -> bool {
@ -536,15 +571,7 @@ impl SurfaceInteraction {
} }
Self { Self {
common: InteractionData { common: InteractionBase::new_surface_geom(pi, n, uv, wo, time),
pi,
n,
time,
wo,
medium_interface: None,
medium: None,
},
uv,
dpdu, dpdu,
dpdv, dpdv,
dndu, dndu,
@ -556,16 +583,16 @@ impl SurfaceInteraction {
dndu, dndu,
dndv, dndv,
}, },
material: None, material: Ptr::null(),
face_index: 0, face_index: 0,
area_light: None, area_light: Ptr::null(),
dpdx: Vector3f::zero(), dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(), dpdy: Vector3f::zero(),
dudx: 0.0, dudx: 0.0,
dudy: 0.0, dudy: 0.0,
dvdx: 0.0, dvdx: 0.0,
dvdy: 0.0, dvdy: 0.0,
shape: core::ptr::null(), shape: Ptr::null(),
} }
} }
@ -579,7 +606,7 @@ impl SurfaceInteraction {
dndv: Normal3f, dndv: Normal3f,
time: Float, time: Float,
flip: bool, flip: bool,
face_index: usize, face_index: u32,
) -> Self { ) -> Self {
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip); let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
si.face_index = face_index; si.face_index = face_index;
@ -597,7 +624,7 @@ impl SurfaceInteraction {
) { ) {
self.shading.n = ns; self.shading.n = ns;
if orientation { if orientation {
self.common.n = self.n().face_forward(self.shading.n.into()); self.common.n = self.n().face_forward(self.shading.n);
} }
self.shading.dpdu = dpdus; self.shading.dpdu = dpdus;
self.shading.dpdv = dpdvs; self.shading.dpdv = dpdvs;
@ -607,26 +634,18 @@ impl SurfaceInteraction {
pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self { pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self {
Self { Self {
common: InteractionData { common: InteractionBase::new_surface_geom(pi, n, uv, Vector3f::zero(), 0.),
pi,
n,
time: 0.,
wo: Vector3f::zero(),
medium_interface: None,
medium: None,
},
uv,
..Default::default() ..Default::default()
} }
} }
pub fn new_minimal(pi: Point3fi, uv: Point2f) -> Self { pub fn new_minimal(pi: Point3fi, uv: Point2f) -> Self {
Self { Self {
common: InteractionData { common: InteractionBase {
pi, pi,
uv,
..Default::default() ..Default::default()
}, },
uv,
..Default::default() ..Default::default()
} }
} }
@ -634,18 +653,18 @@ impl SurfaceInteraction {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
pub fn set_intersection_properties( pub fn set_intersection_properties(
&mut self, &mut self,
mtl: *const Material, mtl: &Material,
area: *const Light, area: &Light,
ray_medium: &Medium,
prim_medium_interface: MediumInterface, prim_medium_interface: MediumInterface,
ray_medium: *const Medium,
) { ) {
self.material = mtl; self.material = Ptr::from(mtl);
self.area_light = area; self.area_light = Ptr::from(area);
if prim_medium_interface.is_medium_transition() { if prim_medium_interface.is_medium_transition() {
self.common.medium_interface = *prim_medium_interface; self.common.medium_interface = prim_medium_interface;
} else { } else {
self.common.medium = ray_medium; self.common.medium = Ptr::from(ray_medium);
} }
} }
} }
@ -653,10 +672,8 @@ impl SurfaceInteraction {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct MediumInteraction { pub struct MediumInteraction {
pub common: InteractionData, pub common: InteractionBase,
pub medium: *const Medium,
pub phase: PhaseFunction, pub phase: PhaseFunction,
pub medium_interface: MediumInterface,
} }
impl MediumInteraction { impl MediumInteraction {
@ -664,21 +681,12 @@ impl MediumInteraction {
p: Point3f, p: Point3f,
wo: Vector3f, wo: Vector3f,
time: Float, time: Float,
medium: *const Medium, medium: Ptr<Medium>,
phase: PhaseFunction, phase: PhaseFunction,
) -> Self { ) -> Self {
Self { Self {
common: InteractionData { common: InteractionBase::new_medium(p, wo, time, medium),
pi: Point3fi::new_from_point(p),
n: Normal3f::default(),
time,
wo: wo.normalize(),
medium_interface: None,
medium,
},
medium,
phase, phase,
medium_interface: MediumInterface::empty(),
} }
} }
} }
@ -688,11 +696,11 @@ impl InteractionTrait for MediumInteraction {
true true
} }
fn get_common(&self) -> &InteractionData { fn get_common(&self) -> &InteractionBase {
&self.common &self.common
} }
fn get_common_mut(&mut self) -> &mut InteractionData { fn get_common_mut(&mut self) -> &mut InteractionBase {
&mut self.common &mut self.common
} }
} }

View file

@ -3,13 +3,13 @@ use crate::core::geometry::{
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
Vector3f, VectorLike, cos_theta, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::image::Image;
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionData, InteractionTrait, MediumInteraction, SimpleInteraction, Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction,
SurfaceInteraction, SurfaceInteraction,
}; };
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::images::Image;
use crate::lights::*; use crate::lights::*;
use crate::spectra::{ use crate::spectra::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum,
@ -17,6 +17,7 @@ use crate::spectra::{
}; };
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square}; use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
use crate::utils::ptr::Ptr;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::{Float, PI}; use crate::{Float, PI};
use bitflags::bitflags; use bitflags::bitflags;
@ -49,9 +50,9 @@ impl LightType {
pub struct LightLeSample { pub struct LightLeSample {
pub l: SampledSpectrum, pub l: SampledSpectrum,
pub ray: Ray, pub ray: Ray,
pub intr: *const InteractionData,
pub pdf_pos: Float, pub pdf_pos: Float,
pub pdf_dir: Float, pub pdf_dir: Float,
pub intr: Interaction,
} }
#[repr(C)] #[repr(C)]
@ -60,12 +61,12 @@ pub struct LightLiSample {
pub l: SampledSpectrum, pub l: SampledSpectrum,
pub wi: Vector3f, pub wi: Vector3f,
pub pdf: Float, pub pdf: Float,
pub p_light: InteractionData, pub p_light: Interaction,
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
impl LightLiSample { impl LightLiSample {
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: InteractionData) -> Self { pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
Self { Self {
l, l,
wi, wi,
@ -140,32 +141,23 @@ impl From<&Interaction> for LightSampleContext {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct LightBase { pub struct LightBase {
pub render_from_light: Transform, pub render_from_light: Transform,
pub light_type: u32, pub light_type: LightType,
pub medium_interface: MediumInterface, pub medium_interface: MediumInterface,
} }
#[cfg(not(target_os = "cuda"))]
impl LightBase { impl LightBase {
pub fn new( pub fn new(
light_type: LightType, light_type: LightType,
render_from_light: &Transform, render_from_light: Transform,
medium_interface: &MediumInterface, medium_interface: MediumInterface,
) -> Self { ) -> Self {
Self { Self {
light_type, light_type,
render_from_light: *render_from_light, render_from_light,
medium_interface: medium_interface.clone(), medium_interface,
} }
} }
pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrum {
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
cache.lookup(dense_spectrum).as_ref()
}
}
impl LightBase {
fn l( fn l(
&self, &self,
_p: Point3f, _p: Point3f,

View file

@ -1,21 +1,23 @@
use crate::materials::*;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc;
use crate::core::bssrdf::BSSRDF; use crate::Float;
use crate::core::bxdf::{ use crate::bxdfs::{
BSDF, BxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF,
}; };
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike}; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::core::interaction::InteractionTrait; use crate::core::image::{Image, WrapMode, WrapMode2D};
use crate::core::interaction::{Interaction, ShadingGeom, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction};
use crate::core::pbrt::Float;
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{ use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator, GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator,
}; };
use crate::images::{Image, WrapMode, WrapMode2D}; use crate::materials::*;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
@ -63,14 +65,14 @@ pub struct NormalBumpEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: usize, pub face_index: u32,
} }
impl From<&SurfaceInteraction> for NormalBumpEvalContext { impl From<&SurfaceInteraction> for NormalBumpEvalContext {
fn from(si: &SurfaceInteraction) -> Self { fn from(si: &SurfaceInteraction) -> Self {
Self { Self {
p: si.p(), p: si.p(),
uv: si.uv, uv: si.common.uv,
n: si.n(), n: si.n(),
shading: si.shading.clone(), shading: si.shading.clone(),
dudx: si.dudx, dudx: si.dudx,
@ -123,7 +125,7 @@ pub fn bump_map<T: TextureEvaluator>(
displacement: &GPUFloatTexture, displacement: &GPUFloatTexture,
ctx: &NormalBumpEvalContext, ctx: &NormalBumpEvalContext,
) -> (Vector3f, Vector3f) { ) -> (Vector3f, Vector3f) {
debug_assert!(tex_eval.can_evaluate(&[displacement], &[])); debug_assert!(tex_eval.can_evaluate(&[Ptr::from(displacement)], &[]));
let mut du = 0.5 * (ctx.dudx.abs() + ctx.dudy.abs()); let mut du = 0.5 * (ctx.dudx.abs() + ctx.dudy.abs());
if du == 0.0 { if du == 0.0 {
du = 0.0005; du = 0.0005;
@ -155,7 +157,7 @@ pub fn bump_map<T: TextureEvaluator>(
} }
#[enum_dispatch] #[enum_dispatch]
pub trait MaterialTrait: Send + Sync + std::fmt::Debug { pub trait MaterialTrait {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
tex_eval: &T, tex_eval: &T,
@ -171,9 +173,9 @@ pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
) -> Option<BSSRDF>; ) -> Option<BSSRDF>;
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool; fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
fn get_normal_map(&self) -> *const Image; fn get_normal_map(&self) -> Option<&Image>;
fn get_displacement(&self) -> Option<GPUFloatTexture>; fn get_displacement(&self) -> Ptr<GPUFloatTexture>;
fn has_surface_scattering(&self) -> bool; fn has_subsurface_scattering(&self) -> bool;
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -191,729 +193,3 @@ pub enum Material {
ThinDielectric(ThinDielectricMaterial), ThinDielectric(ThinDielectricMaterial),
Mix(MixMaterial), Mix(MixMaterial),
} }
#[derive(Clone, Debug)]
pub struct CoatedDiffuseMaterial {
pub displacement: GPUFloatTexture,
pub normal_map: *const Image,
pub reflectance: GPUSpectrumTexture,
pub albedo: GPUSpectrumTexture,
pub u_roughness: GPUFloatTexture,
pub v_roughness: GPUFloatTexture,
pub thickness: GPUFloatTexture,
pub g: GPUFloatTexture,
pub eta: Spectrum,
pub remap_roughness: bool,
pub max_depth: usize,
pub n_samples: usize,
}
impl CoatedDiffuseMaterial {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
reflectance: GPUSpectrumTexture,
u_roughness: GPUFloatTexture,
v_roughness: GPUFloatTexture,
thickness: GPUFloatTexture,
albedo: GPUSpectrumTexture,
g: GPUFloatTexture,
eta: Spectrum,
displacement: GPUFloatTexture,
normal_map: *const Image,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
) -> Self {
Self {
displacement,
normal_map,
reflectance,
albedo,
u_roughness,
v_roughness,
thickness,
g,
eta,
remap_roughness,
max_depth,
n_samples,
}
}
}
impl MaterialTrait for CoatedDiffuseMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
0.,
1.,
);
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx);
let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx);
if self.remap_roughness {
u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough);
v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough);
}
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if self.eta.is_constant() {
let mut lambda = *lambda;
lambda.terminate_secondary_inplace();
}
if sampled_eta == 0. {
sampled_eta = 1.
}
let a = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
0.,
1.,
);
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
let bxdf = BxDF::CoatedDiffuse(CoatedDiffuseBxDF::new(
DielectricBxDF::new(sampled_eta, distrib),
DiffuseBxDF::new(r),
thick,
a,
gg,
self.max_depth,
self.n_samples,
));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(
&[
&self.u_roughness,
&self.v_roughness,
&self.thickness,
&self.g,
],
&[&self.reflectance, &self.albedo],
)
}
fn get_normal_map(&self) -> *const Image {
self.normal_map
}
fn get_displacement(&self) -> Option<FloatTexture> {
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial {
displacement: FloatTexture,
normal_map: *const Image,
interface_uroughness: FloatTexture,
interface_vroughness: FloatTexture,
thickness: FloatTexture,
interface_eta: Spectrum,
g: FloatTexture,
albedo: SpectrumTexture,
conductor_uroughness: FloatTexture,
conductor_vroughness: FloatTexture,
conductor_eta: Option<SpectrumTexture>,
k: Option<SpectrumTexture>,
reflectance: SpectrumTexture,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
}
impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
displacement: FloatTexture,
normal_map: Option<Arc<Image>>,
interface_uroughness: FloatTexture,
interface_vroughness: FloatTexture,
thickness: FloatTexture,
interface_eta: Spectrum,
g: FloatTexture,
albedo: SpectrumTexture,
conductor_uroughness: FloatTexture,
conductor_vroughness: FloatTexture,
conductor_eta: Option<SpectrumTexture>,
k: Option<SpectrumTexture>,
reflectance: SpectrumTexture,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
) -> Self {
Self {
displacement,
normal_map,
interface_uroughness,
interface_vroughness,
thickness,
interface_eta,
g,
albedo,
conductor_uroughness,
conductor_vroughness,
conductor_eta,
k,
reflectance,
remap_roughness,
max_depth,
n_samples,
}
}
}
impl MaterialTrait for CoatedConductorMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let mut iurough = tex_eval.evaluate_float(&self.interface_uroughness, ctx);
let mut ivrough = tex_eval.evaluate_float(&self.interface_vroughness, ctx);
if self.remap_roughness {
iurough = TrowbridgeReitzDistribution::roughness_to_alpha(iurough);
ivrough = TrowbridgeReitzDistribution::roughness_to_alpha(ivrough);
}
let interface_distrib = TrowbridgeReitzDistribution::new(iurough, ivrough);
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
let mut ieta = self.interface_eta.evaluate(lambda[0]);
if self.interface_eta.is_constant() {
let mut lambda = *lambda;
lambda.terminate_secondary_inplace();
}
if ieta == 0. {
ieta = 1.;
}
let (mut ce, mut ck) = if let Some(eta_tex) = &self.conductor_eta {
let k_tex = self
.k
.as_ref()
.expect("CoatedConductor: 'k' must be provided if 'conductor_eta' is present");
let ce = tex_eval.evaluate_spectrum(eta_tex, ctx, lambda);
let ck = tex_eval.evaluate_spectrum(k_tex, ctx, lambda);
(ce, ck)
} else {
let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
0.,
0.9999,
);
let ce = SampledSpectrum::new(1.0);
let one_minus_r = SampledSpectrum::new(1.) - r;
let ck = 2. * r.sqrt() / SampledSpectrum::clamp_zero(&one_minus_r).sqrt();
(ce, ck)
};
ce /= ieta;
ck /= ieta;
let mut curough = tex_eval.evaluate_float(&self.conductor_uroughness, ctx);
let mut cvrough = tex_eval.evaluate_float(&self.conductor_vroughness, ctx);
if self.remap_roughness {
curough = TrowbridgeReitzDistribution::roughness_to_alpha(curough);
cvrough = TrowbridgeReitzDistribution::roughness_to_alpha(cvrough);
}
let conductor_distrib = TrowbridgeReitzDistribution::new(curough, cvrough);
let a = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
0.,
1.,
);
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
let bxdf = BxDF::CoatedConductor(CoatedConductorBxDF::new(
DielectricBxDF::new(ieta, interface_distrib),
ConductorBxDF::new(&conductor_distrib, ce, ck),
thick,
a,
gg,
self.max_depth,
self.n_samples,
));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
let float_textures = [
&self.interface_uroughness,
&self.interface_vroughness,
&self.thickness,
&self.g,
&self.conductor_uroughness,
&self.conductor_vroughness,
];
let mut spectrum_textures = Vec::with_capacity(4);
spectrum_textures.push(&self.albedo);
if let Some(eta) = &self.conductor_eta {
spectrum_textures.push(eta);
}
if let Some(k) = &self.k {
spectrum_textures.push(k);
}
if self.conductor_eta.is_none() {
spectrum_textures.push(&self.reflectance);
}
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
}
fn get_normal_map(&self) -> *const Image {
self.normal_map
}
fn get_displacement(&self) -> Option<FloatTexture> {
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ConductorMaterial;
impl MaterialTrait for ConductorMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> *const Image {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DielectricMaterial {
normal_map: *const Image,
displacement: FloatTexture,
u_roughness: FloatTexture,
v_roughness: FloatTexture,
remap_roughness: bool,
eta: Spectrum,
}
impl MaterialTrait for DielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if !self.eta.is_constant() {
lambda.terminate_secondary();
}
if sampled_eta == 0.0 {
sampled_eta = 1.0;
}
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx);
let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx);
if self.remap_roughness {
u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough);
v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough);
}
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[&self.u_roughness, &self.v_roughness], &[])
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
self.normal_map.clone()
}
fn get_displacement(&self) -> Option<FloatTexture> {
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseMaterial {
normal_map: *const Image,
displacement: FloatTexture,
reflectance: SpectrumTexture,
}
impl MaterialTrait for DiffuseMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
BSDF::new(ctx.ns, ctx.dpdus, Some(bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[], &[&self.reflectance])
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
self.normal_map.clone()
}
fn get_displacement(&self) -> Option<FloatTexture> {
Some(self.displacement.clone())
}
fn has_surface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseTransmissionMaterial;
impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HairMaterial;
impl MaterialTrait for HairMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MeasuredMaterial;
impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SubsurfaceMaterial;
impl MaterialTrait for SubsurfaceMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ThinDielectricMaterial;
impl MaterialTrait for ThinDielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
todo!()
}
fn get_displacement(&self) -> Option<FloatTexture> {
todo!()
}
fn has_surface_scattering(&self) -> bool {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MixMaterial {
pub amount: FloatTexture,
pub materials: [Ptr<Material>; 2],
}
impl MixMaterial {
pub fn choose_material<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
) -> Option<&Material> {
let amt = tex_eval.evaluate_float(&self.amount, ctx);
let index = if amt <= 0.0 {
0
} else if amt >= 1.0 {
1
} else {
let u = hash_float(&(ctx.p, ctx.wo));
if amt < u { 0 } else { 1 }
};
self.materials[index].get()
}
}
impl MaterialTrait for MixMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
if let Some(mat) = self.choose_material(tex_eval, ctx) {
mat.get_bsdf(tex_eval, ctx, lambda)
} else {
BSDF::empty()
}
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[&self.amount], &[])
}
fn get_normal_map(&self) -> Option<Arc<Image>> {
None
}
fn get_displacement(&self) -> Option<FloatTexture> {
panic!(
"MixMaterial::get_displacement() shouldn't be called. \
Displacement is not supported on Mix materials directly."
);
}
fn has_surface_scattering(&self) -> bool {
false
}
}

View file

@ -5,14 +5,16 @@ use crate::core::geometry::{
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction, Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction,
}; };
use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::core::pbrt::{Float, INV_4_PI, PI};
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{ use crate::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::containers::SampledGrid; use crate::utils::containers::SampledGrid;
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};
use crate::utils::ptr::Ptr;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::transform::TransformGeneric; use crate::utils::transform::Transform;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -37,7 +39,7 @@ pub trait PhaseFunctionTrait {
#[repr(C)] #[repr(C)]
#[enum_dispatch(PhaseFunctionTrait)] #[enum_dispatch(PhaseFunctionTrait)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub enum PhaseFunction { pub enum PhaseFunction {
HenyeyGreenstein(HGPhaseFunction), HenyeyGreenstein(HGPhaseFunction),
} }
@ -88,7 +90,7 @@ impl PhaseFunctionTrait for HGPhaseFunction {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct MajorantGrid { pub struct MajorantGrid {
pub bounds: Bounds3f, pub bounds: Bounds3f,
pub res: Point3i, pub res: Point3i,
@ -433,7 +435,8 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
#[enum_dispatch(MediumTrait)] #[enum_dispatch(MediumTrait)]
pub enum Medium { pub enum Medium {
Homogeneous(HomogeneousMedium), Homogeneous(HomogeneousMedium),
@ -443,7 +446,8 @@ pub enum Medium {
NanoVDB(NanoVDBMedium), NanoVDB(NanoVDBMedium),
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct HomogeneousMedium { pub struct HomogeneousMedium {
sigma_a_spec: DenselySampledSpectrum, sigma_a_spec: DenselySampledSpectrum,
sigma_s_spec: DenselySampledSpectrum, sigma_s_spec: DenselySampledSpectrum,
@ -514,7 +518,7 @@ impl MediumTrait for HomogeneousMedium {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GridMedium { pub struct GridMedium {
bounds: Bounds3f, bounds: Bounds3f,
render_from_medium: TransformGeneric<Float>, render_from_medium: Transform,
sigma_a_spec: DenselySampledSpectrum, sigma_a_spec: DenselySampledSpectrum,
sigma_s_spec: DenselySampledSpectrum, sigma_s_spec: DenselySampledSpectrum,
density_grid: SampledGrid<Float>, density_grid: SampledGrid<Float>,
@ -676,7 +680,7 @@ impl RGBGridMedium {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
bounds: &Bounds3f, bounds: &Bounds3f,
render_from_medium: &TransformGeneric<Float>, render_from_medium: &Transform,
g: Float, g: Float,
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>, sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>, sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
@ -825,8 +829,8 @@ impl MediumTrait for NanoVDBMedium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct MediumInterface { pub struct MediumInterface {
pub inside: *const Medium, pub inside: Ptr<Medium>,
pub outside: *const Medium, pub outside: Ptr<Medium>,
} }
unsafe impl Send for MediumInterface {} unsafe impl Send for MediumInterface {}
@ -835,25 +839,25 @@ unsafe impl Sync for MediumInterface {}
impl Default for MediumInterface { impl Default for MediumInterface {
fn default() -> Self { fn default() -> Self {
Self { Self {
inside: core::ptr::null(), inside: Ptr::null(),
outside: core::ptr::null(), outside: Ptr::null(),
} }
} }
} }
impl MediumInterface { impl MediumInterface {
pub fn new(inside: *const Medium, outside: *const Medium) -> Self { pub fn new(inside: &Medium, outside: &Medium) -> Self {
Self { inside, outside } Self {
inside: Ptr::from(inside),
outside: Ptr::from(outside),
}
} }
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self::default()
inside: core::ptr::null(),
outside: core::ptr::null(),
}
} }
pub fn is_medium_transition(&self) -> bool { pub fn is_medium_transition(&self) -> bool {
self.inside != self.outside self.inside.0 != self.outside.0
} }
} }

View file

@ -1,4 +1,4 @@
pub mod aggregates; pub mod bsdf;
pub mod bssrdf; pub mod bssrdf;
pub mod bxdf; pub mod bxdf;
pub mod camera; pub mod camera;
@ -6,6 +6,7 @@ pub mod color;
pub mod film; pub mod film;
pub mod filter; pub mod filter;
pub mod geometry; pub mod geometry;
pub mod image;
pub mod interaction; pub mod interaction;
pub mod light; pub mod light;
pub mod material; pub mod material;
@ -15,5 +16,6 @@ pub mod pbrt;
pub mod primitive; pub mod primitive;
pub mod sampler; pub mod sampler;
pub mod scattering; pub mod scattering;
pub mod shape;
pub mod spectrum; pub mod spectrum;
pub mod texture; pub mod texture;

View file

@ -1,8 +1,8 @@
use crate::core::geometry::Lerp; use crate::core::geometry::Lerp;
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
use num_traits::{Num, PrimInt}; use num_traits::{Num, PrimInt};
use std::hash::Hash; use std::hash::Hash;
use std::ops::{Add, Mul}; use std::ops::{Add, Mul};
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
pub type Float = f32; pub type Float = f32;
@ -101,35 +101,27 @@ pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80; pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
#[inline] #[inline]
pub fn find_interval<T, P>(sz: T, pred: P) -> T pub fn find_interval<F>(sz: u32, pred: F) -> u32
where where
T: PrimInt, F: Fn(u32) -> bool,
P: Fn(T) -> bool,
{ {
let zero = T::zero(); let mut first = 0;
let one = T::one(); let mut len = sz;
let two = one + one;
if sz <= two { while len > 0 {
return zero; let half = len >> 1;
} let middle = first + half;
let mut low = one; if pred(middle) {
let mut high = sz - one; first = middle + 1;
len -= half + 1;
while low < high {
// mid = low + (high - low) / 2
let mid = low + (high - low) / two;
if pred(mid) {
low = mid + one;
} else { } else {
high = mid; len = half;
} }
} }
let result = low - one; let ret = (first as i32 - 1).max(0) as u32;
ret.min(sz.saturating_sub(2))
num_traits::clamp(result, zero, sz - two)
} }
#[inline] #[inline]
@ -144,6 +136,7 @@ pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
#[macro_export] #[macro_export]
macro_rules! check_rare { macro_rules! check_rare {
($frequency_threshold:expr, $condition:expr) => { ($frequency_threshold:expr, $condition:expr) => {
use core::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
const CHECK_INTERVAL: u64 = 4096; const CHECK_INTERVAL: u64 = 4096;
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed); let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);

View file

@ -5,10 +5,11 @@ use crate::core::light::Light;
use crate::core::material::Material; use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface}; use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::shape::{Shape, ShapeIntersection, ShapeTrait};
use crate::core::texture::{GPUFloatTexture, TextureEvalContext}; use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait}; use crate::utils::ArenaPtr;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::transform::{AnimatedTransform, TransformGeneric}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc; use std::sync::Arc;
@ -87,14 +88,14 @@ impl PrimitiveTrait for GeometricPrimitive {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SimplePrimitive { pub struct SimplePrimitive {
shape: Arc<Shape>, shape: ArenaPtr<Shape>,
material: Arc<Material>, material: ArenaPtr<Material>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TransformedPrimitive { pub struct TransformedPrimitive {
primitive: Arc<dyn PrimitiveTrait>, pub primitive: ArenaPtr<Primitive>,
render_from_primitive: TransformGeneric<Float>, pub render_from_primitive: Transform,
} }
impl PrimitiveTrait for TransformedPrimitive { impl PrimitiveTrait for TransformedPrimitive {
@ -125,9 +126,10 @@ impl PrimitiveTrait for TransformedPrimitive {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct AnimatedPrimitive { pub struct AnimatedPrimitive {
primitive: Arc<dyn PrimitiveTrait>, primitive: ArenaPtr<Primitive>,
render_from_primitive: AnimatedTransform, render_from_primitive: AnimatedTransform,
} }
@ -158,11 +160,12 @@ impl PrimitiveTrait for AnimatedPrimitive {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BVHAggregatePrimitive { pub struct BVHAggregatePrimitive {
max_prims_in_node: usize, max_prims_in_node: u32,
primitives: Vec<Arc<dyn PrimitiveTrait>>, primitives: *const ArenaPtr<Primitive>,
nodes: Vec<LinearBVHNode>, nodes: *const LinearBVHNode,
} }
impl PrimitiveTrait for BVHAggregatePrimitive { impl PrimitiveTrait for BVHAggregatePrimitive {

View file

@ -1,14 +1,9 @@
use std::ops::RangeFull;
use enum_dispatch::enum_dispatch;
use rand::seq::index::sample;
use crate::core::filter::FilterTrait; use crate::core::filter::FilterTrait;
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
use crate::core::options::{PBRTOptions, get_options}; use crate::core::options::{PBRTOptions, get_options};
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, find_interval}; use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, find_interval};
use crate::utils::Ptr;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use crate::utils::error::FileLoc;
use crate::utils::math::{ use crate::utils::math::{
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
PRIME_TABLE_SIZE, Scrambler, clamp, compute_radical_inverse_permutations, encode_morton_2, PRIME_TABLE_SIZE, Scrambler, clamp, compute_radical_inverse_permutations, encode_morton_2,
@ -16,12 +11,14 @@ use crate::utils::math::{
radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index, radical_inverse, round_up_pow2, scrambled_radical_inverse, sobol_interval_to_index,
sobol_sample, sobol_sample,
}; };
use crate::utils::parameters::ParameterDictionary;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::sobol::N_SOBOL_DIMENSIONS; use crate::utils::sobol::N_SOBOL_DIMENSIONS;
use crate::utils::{hash::*, sobol}; use crate::utils::{hash::*, sobol};
use enum_dispatch::enum_dispatch;
use rand::seq::index::sample;
#[derive(Debug, Clone, Copy)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct CameraSample { pub struct CameraSample {
pub p_film: Point2f, pub p_film: Point2f,
pub p_lens: Point2f, pub p_lens: Point2f,
@ -29,17 +26,6 @@ pub struct CameraSample {
pub filter_weight: Float, pub filter_weight: Float,
} }
impl Default for CameraSample {
fn default() -> Self {
Self {
p_film: Point2f::default(),
p_lens: Point2f::default(),
time: 0.0,
filter_weight: 1.0,
}
}
}
pub fn get_camera_sample<S, F>(sampler: &mut S, p_pixel: Point2i, filter: &F) -> CameraSample pub fn get_camera_sample<S, F>(sampler: &mut S, p_pixel: Point2i, filter: &F) -> CameraSample
where where
S: SamplerTrait, S: SamplerTrait,
@ -54,43 +40,29 @@ where
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct IndependentSampler { pub struct IndependentSampler {
samples_per_pixel: usize, pub samples_per_pixel: u32,
seed: u64, pub seed: u64,
rng: Rng, pub rng: Rng,
} }
impl IndependentSampler { impl IndependentSampler {
pub fn new(samples_per_pixel: usize, seed: u64) -> Self { pub fn new(samples_per_pixel: u32, seed: u64) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed, seed,
rng: Rng::default(), rng: Rng::default(),
} }
} }
pub fn create(
params: &ParameterDictionary,
_full_res: Point2i,
_loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
Ok(Self::new(nsamp as usize, seed as u64))
}
} }
impl SamplerTrait for IndependentSampler { impl SamplerTrait for IndependentSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
let sequence_index = hash_buffer(&hash_input, 0); let sequence_index = hash_buffer(&hash_input, 0);
self.rng.set_sequence(sequence_index); self.rng.set_sequence(sequence_index);
@ -111,7 +83,8 @@ impl SamplerTrait for IndependentSampler {
const MAX_HALTON_RESOLUTION: i32 = 128; const MAX_HALTON_RESOLUTION: i32 = 128;
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[repr(C)]
#[derive(Debug, Default, Clone, PartialEq, Eq, Copy)]
pub enum RandomizeStrategy { pub enum RandomizeStrategy {
#[default] #[default]
None, None,
@ -120,21 +93,22 @@ pub enum RandomizeStrategy {
Owen, Owen,
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct HaltonSampler { pub struct HaltonSampler {
samples_per_pixel: usize, samples_per_pixel: u32,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
digit_permutations: Vec<DigitPermutation>,
base_scales: [u64; 2], base_scales: [u64; 2],
base_exponents: [u64; 2], base_exponents: [u64; 2],
mult_inverse: [u64; 2], mult_inverse: [u64; 2],
halton_index: u64, halton_index: u64,
dim: usize, dim: u32,
digit_permutations: Ptr<DigitPermutation>,
} }
impl HaltonSampler { impl HaltonSampler {
pub fn new( pub fn new(
samples_per_pixel: usize, samples_per_pixel: u32,
full_res: Point2i, full_res: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: u64, seed: u64,
@ -180,14 +154,14 @@ impl HaltonSampler {
} }
} }
fn sample_dimension(&self, dimension: usize) -> Float { fn sample_dimension(&self, dimension: u32) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
radical_inverse(dimension, self.halton_index) radical_inverse(dimension, self.halton_index)
} else if self.randomize == RandomizeStrategy::PermuteDigits { } else if self.randomize == RandomizeStrategy::PermuteDigits {
scrambled_radical_inverse( scrambled_radical_inverse(
dimension, dimension,
self.halton_index, self.halton_index,
&self.digit_permutations[dimension], &self.digit_permutations[dimension as usize],
) )
} else { } else {
owen_scrambled_radical_inverse( owen_scrambled_radical_inverse(
@ -214,45 +188,14 @@ impl HaltonSampler {
(yp, xp - d * yp) (yp, xp - d * yp)
} }
pub fn create(
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params
.get_one_string("randomization", "permutedigits")
.as_str()
{
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for Halton",
loc
));
}
};
Ok(HaltonSampler::new(nsamp as usize, full_res, s, seed as u64))
}
} }
impl SamplerTrait for HaltonSampler { impl SamplerTrait for HaltonSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
self.halton_index = 0; self.halton_index = 0;
let sample_stride = self.base_scales[0] * self.base_scales[1]; let sample_stride = self.base_scales[0] * self.base_scales[1];
@ -287,14 +230,14 @@ impl SamplerTrait for HaltonSampler {
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
if self.dim > PRIME_TABLE_SIZE { if self.dim > PRIME_TABLE_SIZE as u32 {
self.dim = 2; self.dim = 2;
} }
self.sample_dimension(self.dim) self.sample_dimension(self.dim)
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
if self.dim > PRIME_TABLE_SIZE { if self.dim > PRIME_TABLE_SIZE as u32 {
self.dim = 2; self.dim = 2;
} }
let dim = self.dim; let dim = self.dim;
@ -310,22 +253,23 @@ impl SamplerTrait for HaltonSampler {
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct StratifiedSampler { pub struct StratifiedSampler {
x_pixel_samples: usize, x_pixel_samples: u32,
y_pixel_samples: usize, y_pixel_samples: u32,
jitter: bool, jitter: bool,
seed: u64, seed: u64,
rng: Rng, rng: Rng,
pixel: Point2i, pixel: Point2i,
sample_index: usize, sample_index: u32,
dim: usize, dim: u32,
} }
impl StratifiedSampler { impl StratifiedSampler {
pub fn new( pub fn new(
x_pixel_samples: usize, x_pixel_samples: u32,
y_pixel_samples: usize, y_pixel_samples: u32,
seed: Option<u64>, seed: Option<u64>,
jitter: bool, jitter: bool,
) -> Self { ) -> Self {
@ -340,43 +284,14 @@ impl StratifiedSampler {
dim: 0, dim: 0,
} }
} }
pub fn create(
params: &ParameterDictionary,
_full_res: Point2i,
_loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let jitter = params.get_one_bool("jitter", true);
let (x_samples, y_samples) = if options.quick_render {
(1, 1)
} else if let Some(n) = options.pixel_samples {
let div = (n as f64).sqrt() as i32;
let y = (1..=div).rev().find(|d| n % d == 0).unwrap();
(n / y, y)
} else {
(
params.get_one_int("xsamples", 4),
params.get_one_int("ysamples", 4),
)
};
let seed = params.get_one_int("seed", options.seed);
Ok(Self::new(
x_samples as usize,
y_samples as usize,
Some(seed as u64),
jitter,
))
}
} }
impl SamplerTrait for StratifiedSampler { impl SamplerTrait for StratifiedSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> u32 {
self.x_pixel_samples * self.y_pixel_samples self.x_pixel_samples * self.y_pixel_samples
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
@ -446,18 +361,19 @@ impl SamplerTrait for StratifiedSampler {
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)]
pub struct PaddedSobolSampler { pub struct PaddedSobolSampler {
samples_per_pixel: usize, samples_per_pixel: u32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
pixel: Point2i, pixel: Point2i,
sample_index: usize, sample_index: u32,
dim: usize, dim: u32,
} }
impl PaddedSobolSampler { impl PaddedSobolSampler {
pub fn new(samples_per_pixel: usize, randomize: RandomizeStrategy, seed: Option<u64>) -> Self { pub fn new(samples_per_pixel: u32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed: seed.unwrap_or(0), seed: seed.unwrap_or(0),
@ -468,7 +384,7 @@ impl PaddedSobolSampler {
} }
} }
fn sample_dimension(&self, dimension: usize, a: u32, hash: u32) -> Float { fn sample_dimension(&self, dimension: u32, a: u32, hash: u32) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
return sobol_sample(a as u64, dimension, NoRandomizer); return sobol_sample(a as u64, dimension, NoRandomizer);
} }
@ -483,41 +399,13 @@ impl PaddedSobolSampler {
RandomizeStrategy::None => unreachable!(), RandomizeStrategy::None => unreachable!(),
} }
} }
pub fn create(
params: &ParameterDictionary,
_full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params.get_one_string("randomization", "fastowen").as_str() {
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for ZSobol",
loc
));
}
};
Ok(Self::new(nsamp as usize, s, Some(seed as u64)))
}
} }
impl SamplerTrait for PaddedSobolSampler { impl SamplerTrait for PaddedSobolSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
@ -565,18 +453,18 @@ impl SamplerTrait for PaddedSobolSampler {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct SobolSampler { pub struct SobolSampler {
samples_per_pixel: usize, samples_per_pixel: u32,
scale: i32, scale: i32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
pixel: Point2i, pixel: Point2i,
dim: usize, dim: u32,
sobol_index: u64, sobol_index: u64,
} }
impl SobolSampler { impl SobolSampler {
pub fn new( pub fn new(
samples_per_pixel: usize, samples_per_pixel: u32,
full_resolution: Point2i, full_resolution: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: Option<u64>, seed: Option<u64>,
@ -593,7 +481,7 @@ impl SobolSampler {
} }
} }
fn sample_dimension(&self, dimension: usize) -> Float { fn sample_dimension(&self, dimension: u32) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
return sobol_sample(self.sobol_index, dimension, NoRandomizer); return sobol_sample(self.sobol_index, dimension, NoRandomizer);
} }
@ -614,41 +502,13 @@ impl SobolSampler {
RandomizeStrategy::None => unreachable!(), RandomizeStrategy::None => unreachable!(),
} }
} }
pub fn create(
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params.get_one_string("randomization", "fastowen").as_str() {
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for ZSobol",
loc
));
}
};
Ok(Self::new(nsamp as usize, full_res, s, Some(seed as u64)))
}
} }
impl SamplerTrait for SobolSampler { impl SamplerTrait for SobolSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> u32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.dim = 2.max(dim.unwrap_or(0)); self.dim = 2.max(dim.unwrap_or(0));
self.sobol_index = self.sobol_index =
@ -656,7 +516,7 @@ impl SamplerTrait for SobolSampler {
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
if self.dim >= N_SOBOL_DIMENSIONS { if self.dim >= N_SOBOL_DIMENSIONS as u32 {
self.dim = 2; self.dim = 2;
} }
@ -666,7 +526,7 @@ impl SamplerTrait for SobolSampler {
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
if self.dim >= N_SOBOL_DIMENSIONS { if self.dim >= N_SOBOL_DIMENSIONS as u32 {
self.dim = 2; self.dim = 2;
} }
let u = Point2f::new( let u = Point2f::new(
@ -696,14 +556,15 @@ impl SamplerTrait for SobolSampler {
} }
} }
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Copy, Debug, Clone)]
pub struct ZSobolSampler { pub struct ZSobolSampler {
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: u64, seed: u64,
log2_samples_per_pixel: u32, log2_samples_per_pixel: u32,
n_base4_digits: u32, n_base4_digits: u32,
morton_index: u64, morton_index: u64,
dim: usize, dim: u32,
} }
impl ZSobolSampler { impl ZSobolSampler {
@ -769,7 +630,7 @@ impl ZSobolSampler {
let mix_input = higher_digits ^ (0x55555555 * self.dim as u64); let mix_input = higher_digits ^ (0x55555555 * self.dim as u64);
let p = (mix_bits(mix_input) >> 24) % 24; let p = (mix_bits(mix_input) >> 24) % 24;
digit = PERMUTATIONS[p as usize][digit as usize] as u64; digit = PERMUTATIONS[p as u32][digit as u32] as u64;
sample_index |= digit << digit_shift; sample_index |= digit << digit_shift;
} }
@ -781,46 +642,13 @@ impl ZSobolSampler {
sample_index sample_index
} }
pub fn create(
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
let options = get_options();
let nsamp = options
.quick_render
.then_some(1)
.or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed);
let s = match params.get_one_string("randomization", "fastowen").as_str() {
"none" => RandomizeStrategy::None,
"permutedigits" => RandomizeStrategy::PermuteDigits,
"fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen,
_ => {
return Err(format!(
"{}: Unknown randomization strategy for ZSobol",
loc
));
}
};
Ok(ZSobolSampler::new(
nsamp as u32,
full_res,
s,
Some(seed as u64),
))
}
} }
impl SamplerTrait for ZSobolSampler { impl SamplerTrait for ZSobolSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> u32 {
todo!() todo!()
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) {
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32) self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32)
<< self.log2_samples_per_pixel) << self.log2_samples_per_pixel)
@ -886,10 +714,10 @@ impl SamplerTrait for ZSobolSampler {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct MLTSampler; pub struct MLTSampler;
impl SamplerTrait for MLTSampler { impl SamplerTrait for MLTSampler {
fn samples_per_pixel(&self) -> usize { fn samples_per_pixel(&self) -> u32 {
todo!() todo!()
} }
fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: usize, _dim: Option<usize>) { fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: u32, _dim: Option<u32>) {
todo!() todo!()
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
@ -905,8 +733,8 @@ impl SamplerTrait for MLTSampler {
#[enum_dispatch] #[enum_dispatch]
pub trait SamplerTrait { pub trait SamplerTrait {
fn samples_per_pixel(&self) -> usize; fn samples_per_pixel(&self) -> u32;
fn start_pixel_sample(&mut self, p: Point2i, sample_index: usize, dim: Option<usize>); fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>);
fn get1d(&mut self) -> Float; fn get1d(&mut self) -> Float;
fn get2d(&mut self) -> Point2f; fn get2d(&mut self) -> Point2f;
fn get_pixel2d(&mut self) -> Point2f; fn get_pixel2d(&mut self) -> Point2f;
@ -923,40 +751,3 @@ pub enum Sampler {
ZSobol(ZSobolSampler), ZSobol(ZSobolSampler),
MLT(MLTSampler), MLT(MLTSampler),
} }
impl Sampler {
pub fn create(
name: &str,
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
) -> Result<Self, String> {
match name {
"zsobol" => {
let sampler = ZSobolSampler::create(params, full_res, loc)?;
Ok(Sampler::ZSobol(sampler))
}
"paddedsobol" => {
let sampler = PaddedSobolSampler::create(params, full_res, loc)?;
Ok(Sampler::PaddedSobol(sampler))
}
"halton" => {
let sampler = HaltonSampler::create(params, full_res, loc)?;
Ok(Sampler::Halton(sampler))
}
"sobol" => {
let sampler = SobolSampler::create(params, full_res, loc)?;
Ok(Sampler::Sobol(sampler))
}
"Independent" => {
let sampler = IndependentSampler::create(params, full_res, loc)?;
Ok(Sampler::Independent(sampler))
}
"stratified" => {
let sampler = StratifiedSampler::create(params, full_res, loc)?;
Ok(Sampler::Stratified(sampler))
}
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
}
}
}

View file

@ -9,6 +9,7 @@ use crate::utils::sampling::sample_uniform_disk_polar;
use num::complex::Complex; use num::complex::Complex;
#[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct TrowbridgeReitzDistribution { pub struct TrowbridgeReitzDistribution {
alpha_x: Float, alpha_x: Float,
@ -170,3 +171,40 @@ pub fn fr_complex_from_spectrum(
} }
result result
} }
pub fn fresnel_moment1(eta: Float) -> Float {
let eta2 = eta * eta;
let eta3 = eta2 * eta;
let eta4 = eta3 * eta;
let eta5 = eta4 * eta;
if eta < 1. {
return 0.45966 - 1.73965 * eta + 3.37668 * eta2 - 3.904945 * eta3 + 2.49277 * eta4
- 0.68441 * eta5;
} else {
return -4.61686 + 11.1136 * eta - 10.4646 * eta2 + 5.11455 * eta3 - 1.27198 * eta4
+ 0.12746 * eta5;
}
}
pub fn fresnel_moment2(eta: Float) -> Float {
let eta2 = eta * eta;
let eta3 = eta2 * eta;
let eta4 = eta3 * eta;
let eta5 = eta4 * eta;
if eta < 1. {
return 0.27614 - 0.87350 * eta + 1.12077 * eta2 - 0.65095 * eta3
+ 0.07883 * eta4
+ 0.04860 * eta5;
} else {
let r_eta = 1. / eta;
let r_eta2 = r_eta * r_eta;
let r_eta3 = r_eta2 * r_eta;
return -547.033 + 45.3087 * r_eta3 - 218.725 * r_eta2 + 458.843 * r_eta + 404.557 * eta
- 189.519 * eta2
+ 54.9327 * eta3
- 9.00603 * eta4
+ 0.63942 * eta5;
}
}

154
shared/src/core/shape.rs Normal file
View file

@ -0,0 +1,154 @@
use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Vector3fi, VectorLike,
};
use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::light::Light;
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::shapes::*;
use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
use enum_dispatch::enum_dispatch;
// Define Intersection objects. This only varies for
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeIntersection {
pub intr: SurfaceInteraction,
pub t_hit: Float,
}
impl ShapeIntersection {
pub fn new(intr: SurfaceInteraction, t_hit: Float) -> Self {
Self { intr, t_hit }
}
pub fn t_hit(&self) -> Float {
self.t_hit
}
pub fn set_t_hit(&mut self, new_t: Float) {
self.t_hit = new_t;
}
pub fn set_intersection_properties(
&mut self,
mtl: &Material,
area: &Light,
prim_medium_interface: MediumInterface,
ray_medium: &Medium,
) {
self.intr
.set_intersection_properties(mtl, area, ray_medium, prim_medium_interface);
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct QuadricIntersection {
pub t_hit: Float,
pub p_obj: Point3f,
pub phi: Float,
}
impl QuadricIntersection {
pub fn new(t_hit: Float, p_obj: Point3f, phi: Float) -> Self {
Self { t_hit, p_obj, phi }
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeSample {
pub intr: Interaction,
pub pdf: Float,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ShapeSampleContext {
pub pi: Point3fi,
pub n: Normal3f,
pub ns: Normal3f,
pub time: Float,
}
impl ShapeSampleContext {
pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f, time: Float) -> Self {
Self { pi, n, ns, time }
}
pub fn new_from_interaction(si: &SurfaceInteraction) -> Self {
Self {
pi: si.pi(),
n: si.n(),
ns: si.shading.n,
time: si.time(),
}
}
pub fn p(&self) -> Point3f {
Point3f::from(self.pi)
}
pub fn offset_ray_origin(&self, w: Vector3f) -> Point3f {
let d = self.n.abs().dot(self.pi.error().into());
let mut offset = d * Vector3f::from(self.n);
if w.dot(self.n.into()) < 0.0 {
offset = -offset;
}
let mut po = Point3f::from(self.pi) + offset;
for i in 0..3 {
if offset[i] > 0.0 {
po[i] = next_float_up(po[i]);
} else {
po[i] = next_float_down(po[i]);
}
}
po
}
pub fn offset_ray_origin_from_point(&self, pt: Point3f) -> Point3f {
self.offset_ray_origin(pt - self.p())
}
pub fn spawn_ray(&self, w: Vector3f) -> Ray {
Ray::new(self.offset_ray_origin(w), w, Some(self.time), &Ptr::null())
}
}
#[enum_dispatch]
pub trait ShapeTrait {
fn bounds(&self) -> Bounds3f;
fn normal_bounds(&self) -> DirectionCone;
fn area(&self) -> Float;
fn sample(&self, u: Point2f) -> Option<ShapeSample>;
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample>;
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool;
fn pdf(&self, interaction: &Interaction) -> Float;
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float;
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[enum_dispatch(ShapeTrait)]
pub enum Shape {
Sphere(SphereShape),
Cylinder(CylinderShape),
Disk(DiskShape),
Triangle(TriangleShape),
BilinearPatch(BilinearPatchShape),
Curve(CurveShape),
}
impl Default for Shape {
fn default() -> Self {
Shape::Sphere(SphereShape::default())
}
}

View file

@ -43,9 +43,9 @@ impl Spectrum {
} }
pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ { pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ {
let x = self.inner_product(&std.x()); let x = self.inner_product(&Spectrum::Dense(std.x));
let y = self.inner_product(&std.y()); let y = self.inner_product(&Spectrum::Dense(std.y));
let z = self.inner_product(&std.z()); let z = self.inner_product(&Spectrum::Dense(std.z));
XYZ::new(x, y, z) / CIE_Y_INTEGRAL XYZ::new(x, y, z) / CIE_Y_INTEGRAL
} }

View file

@ -2,13 +2,14 @@ use crate::core::color::ColorEncoding;
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
}; };
use crate::core::image::WrapMode;
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::images::WrapMode;
use crate::spectra::{ use crate::spectra::{
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, SampledWavelengths,
}; };
use crate::textures::*; use crate::textures::*;
use crate::utils::Ptr;
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::{Float, INV_2_PI, INV_PI, PI}; use crate::{Float, INV_2_PI, INV_PI, PI};
@ -258,7 +259,7 @@ impl PointTransformMapping {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Default, Debug)]
pub struct TextureEvalContext { pub struct TextureEvalContext {
pub p: Point3f, pub p: Point3f,
pub dpdx: Vector3f, pub dpdx: Vector3f,
@ -269,7 +270,7 @@ pub struct TextureEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: usize, pub face_index: u32,
} }
impl TextureEvalContext { impl TextureEvalContext {
@ -284,7 +285,7 @@ impl TextureEvalContext {
dudy: Float, dudy: Float,
dvdx: Float, dvdx: Float,
dvdy: Float, dvdy: Float,
face_index: usize, face_index: u32,
) -> Self { ) -> Self {
Self { Self {
p, p,
@ -308,7 +309,7 @@ impl From<&SurfaceInteraction> for TextureEvalContext {
dpdx: si.dpdx, dpdx: si.dpdx,
dpdy: si.dpdy, dpdy: si.dpdy,
n: si.common.n, n: si.common.n,
uv: si.uv, uv: si.common.uv,
dudx: si.dudx, dudx: si.dudx,
dudy: si.dudy, dudy: si.dudy,
dvdx: si.dvdx, dvdx: si.dvdx,
@ -363,7 +364,7 @@ impl GPUFloatTexture {
GPUFloatTexture::Dots(t) => t.evaluate(ctx), GPUFloatTexture::Dots(t) => t.evaluate(ctx),
GPUFloatTexture::FBm(t) => t.evaluate(ctx), GPUFloatTexture::FBm(t) => t.evaluate(ctx),
GPUFloatTexture::Windy(t) => t.evaluate(ctx), GPUFloatTexture::Windy(t) => t.evaluate(ctx),
GPUFloatTexture::Wrinkle(t) => t.evaluate(ctx), GPUFloatTexture::Wrinkled(t) => t.evaluate(ctx),
GPUFloatTexture::Ptex(t) => t.evaluate(ctx), GPUFloatTexture::Ptex(t) => t.evaluate(ctx),
GPUFloatTexture::Image(t) => t.evaluate(ctx), GPUFloatTexture::Image(t) => t.evaluate(ctx),
GPUFloatTexture::Mix(t) => t.evaluate(ctx), GPUFloatTexture::Mix(t) => t.evaluate(ctx),
@ -396,7 +397,11 @@ pub enum GPUSpectrumTexture {
} }
impl GPUSpectrumTexture { impl GPUSpectrumTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> Float { pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
match self { match self {
GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda), GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda),
@ -421,7 +426,11 @@ pub trait TextureEvaluator: Send + Sync {
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum; ) -> SampledSpectrum;
fn can_evaluate(&self, _ftex: &[&GPUFloatTexture], _stex: &[&GPUSpectrumTexture]) -> bool; fn can_evaluate(
&self,
_ftex: &[Ptr<GPUFloatTexture>],
_stex: &[Ptr<GPUSpectrumTexture>],
) -> bool;
} }
#[repr(C)] #[repr(C)]
@ -444,8 +453,8 @@ impl TextureEvaluator for UniversalTextureEvaluator {
fn can_evaluate( fn can_evaluate(
&self, &self,
_float_textures: &[&GPUFloatTexture], _float_textures: &[Ptr<GPUFloatTexture>],
_spectrum_textures: &[&GPUSpectrumTexture], _spectrum_textures: &[Ptr<GPUSpectrumTexture>],
) -> bool { ) -> bool {
true true
} }

View file

@ -2,55 +2,39 @@ use crate::Float;
use bytemuck::cast_slice; use bytemuck::cast_slice;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../../data/srgb_scale.dat"); #[repr(C, align(16))]
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../../data/srgb_coeffs.dat"); struct AlignedData<const N: usize>(pub [u8; N]);
pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(SRGB_SCALE_BYTES)); macro_rules! load_static_table {
($name:ident, $path:literal) => {
pub static $name: &[Float] = {
static RAW_DATA: AlignedData<{ include_bytes!($path).len() }> =
AlignedData(*include_bytes!($path));
pub static SRGB_COEFFS: Lazy<&[Float]> = unsafe {
Lazy::new(|| match bytemuck::try_cast_slice(SRGB_COEFFS_BYTES) { let bytes = &RAW_DATA.0;
Ok(s) => s,
Err(_) => { let stride = core::mem::size_of::<Float>();
let v: Vec<Float> = bytemuck::pod_collect_to_vec(SRGB_COEFFS_BYTES); let len = bytes.len() / stride;
Box::leak(v.into_boxed_slice()) debug_assert!(
bytes.len() % stride == 0,
"Data file size is not a multiple of Float size"
);
core::slice::from_raw_parts(bytes.as_ptr() as *const Float, len)
} }
}); };
};
}
static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../../data/dcip3_scale.dat"); load_static_table!(SRGB_SCALE, "../../data/srgb_scale.dat");
static DCI_P3_COEFFS_BYTES: &[u8] = include_bytes!("../../data/dcip3_coeffs.dat"); load_static_table!(SRGB_COEFFS, "../../data/srgb_coeffs.dat");
pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(DCI_P3_SCALE_BYTES));
pub static DCI_P3_COEFFS: Lazy<&[Float]> =
Lazy::new(|| match bytemuck::try_cast_slice(DCI_P3_COEFFS_BYTES) {
Ok(s) => s,
Err(_) => {
let v: Vec<Float> = bytemuck::pod_collect_to_vec(DCI_P3_COEFFS_BYTES);
Box::leak(v.into_boxed_slice())
}
});
static ACES_SCALE_BYTES: &[u8] = include_bytes!("../../data/aces_scale.dat"); load_static_table!(DCI_P3_SCALE, "../../data/dcip3_scale.dat");
static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../../data/aces_coeffs.dat"); load_static_table!(DCI_P3_COEFFS, "../../data/dcip3_coeffs.dat");
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(ACES_SCALE_BYTES)); load_static_table!(ACES_SCALE, "../../data/aces_scale.dat");
load_static_table!(ACES_COEFFS, "../../data/aces_coeffs.dat");
pub static ACES_COEFFS: Lazy<&[Float]> = load_static_table!(REC2020_SCALE, "../../data/rec2020_scale.dat");
Lazy::new(|| match bytemuck::try_cast_slice(ACES_COEFFS_BYTES) { load_static_table!(REC2020_COEFFS, "../../data/rec2020_coeffs.dat");
Ok(s) => s,
Err(_) => {
let v: Vec<Float> = bytemuck::pod_collect_to_vec(ACES_COEFFS_BYTES);
Box::leak(v.into_boxed_slice())
}
});
static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../../data/rec2020_scale.dat");
static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../../data/rec2020_coeffs.dat");
pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(REC2020_SCALE_BYTES));
pub static REC2020_COEFFS: Lazy<&[Float]> =
Lazy::new(|| match bytemuck::try_cast_slice(REC2020_COEFFS_BYTES) {
Ok(s) => s,
Err(_) => {
let v: Vec<Float> = bytemuck::pod_collect_to_vec(REC2020_COEFFS_BYTES);
Box::leak(v.into_boxed_slice())
}
});

View file

@ -1,8 +1,10 @@
use crate::Float; use crate::Float;
use crate::core::filter::FilterSample; use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::lerp;
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct BoxFilter { pub struct BoxFilter {
pub radius: Vector2f, pub radius: Vector2f,
} }
@ -11,12 +13,13 @@ impl BoxFilter {
pub fn new(radius: Vector2f) -> Self { pub fn new(radius: Vector2f) -> Self {
Self { radius } Self { radius }
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for BoxFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() { if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() {
1. 1.
} else { } else {
@ -24,11 +27,11 @@ impl BoxFilter {
} }
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
(2.0 * self.radius.x()) * (2.0 * self.radius.y()) (2.0 * self.radius.x()) * (2.0 * self.radius.y())
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new( let p = Point2f::new(
lerp(u[0], -self.radius.x(), self.radius.x()), lerp(u[0], -self.radius.x(), self.radius.x()),
lerp(u[1], -self.radius.y(), self.radius.y()), lerp(u[1], -self.radius.y(), self.radius.y()),

View file

@ -1,4 +1,10 @@
#[derive(Clone, Debug)] use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{gaussian, gaussian_integral};
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct GaussianFilter { pub struct GaussianFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub sigma: Float, pub sigma: Float,
@ -26,24 +32,26 @@ impl GaussianFilter {
sampler, sampler,
} }
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for GaussianFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
(gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0) (gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0)
* (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0) * (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0)
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma) (gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
- 2.0 * self.radius.x() * self.exp_x) - 2.0 * self.radius.x() * self.exp_x)
* (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma) * (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma)
- 2.0 * self.radius.y() * self.exp_y) - 2.0 * self.radius.y() * self.exp_y)
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,4 +1,11 @@
#[derive(Clone, Debug)] use crate::Float;
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{lerp, windowed_sinc};
use rand::Rng;
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct LanczosSincFilter { pub struct LanczosSincFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub tau: Float, pub tau: Float,
@ -17,17 +24,19 @@ impl LanczosSincFilter {
sampler, sampler,
} }
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for LanczosSincFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
windowed_sinc(p.x(), self.radius.x(), self.tau) windowed_sinc(p.x(), self.radius.x(), self.tau)
* windowed_sinc(p.y(), self.radius.y(), self.tau) * windowed_sinc(p.y(), self.radius.y(), self.tau)
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
let sqrt_samples = 64; let sqrt_samples = 64;
let n_samples = sqrt_samples * sqrt_samples; let n_samples = sqrt_samples * sqrt_samples;
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y()); let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
@ -50,7 +59,7 @@ impl LanczosSincFilter {
sum / n_samples as Float * area sum / n_samples as Float * area
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,8 +1,9 @@
use crate::Float; use crate::Float;
use crate::core::filter::FilterSampler; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MitchellFilter { pub struct MitchellFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub b: Float, pub b: Float,
@ -47,21 +48,23 @@ impl MitchellFilter {
fn mitchell_1d(&self, x: Float) -> Float { fn mitchell_1d(&self, x: Float) -> Float {
Self::mitchell_1d_eval(self.b, self.c, x) Self::mitchell_1d_eval(self.b, self.c, x)
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for MitchellFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
self.mitchell_1d(2.0 * p.x() / self.radius.x()) self.mitchell_1d(2.0 * p.x() / self.radius.x())
* self.mitchell_1d(2.0 * p.y() / self.radius.y()) * self.mitchell_1d(2.0 * p.y() / self.radius.y())
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
self.radius.x() * self.radius.y() / 4.0 self.radius.x() * self.radius.y() / 4.0
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,4 +1,10 @@
#[derive(Clone, Debug)] use crate::Float;
use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::sample_tent;
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct TriangleFilter { pub struct TriangleFilter {
pub radius: Vector2f, pub radius: Vector2f,
} }
@ -7,20 +13,22 @@ impl TriangleFilter {
pub fn new(radius: Vector2f) -> Self { pub fn new(radius: Vector2f) -> Self {
Self { radius } Self { radius }
} }
}
pub fn radius(&self) -> Vector2f { impl FilterTrait for TriangleFilter {
fn radius(&self) -> Vector2f {
self.radius self.radius
} }
pub fn evaluate(&self, p: Point2f) -> Float { fn evaluate(&self, p: Point2f) -> Float {
(self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0) (self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0)
} }
pub fn integral(&self) -> Float { fn integral(&self) -> Float {
self.radius.x().powi(2) * self.radius.y().powi(2) self.radius.x().powi(2) * self.radius.y().powi(2)
} }
pub fn sample(&self, u: Point2f) -> FilterSample { fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new( let p = Point2f::new(
sample_tent(u[0], self.radius.x()), sample_tent(u[0], self.radius.x()),
sample_tent(u[1], self.radius.y()), sample_tent(u[1], self.radius.y()),

View file

@ -1,118 +0,0 @@
use crate::core::geometry::{Bounds2i, Point2i};
use crate::core::pbrt::Float;
use crate::spectra::colorspace::RGBColorSpace;
use crate::utils::math::SquareMatrix;
use smallvec::SmallVec;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
#[derive(Clone, Debug, Default)]
pub struct ImageChannelValues(pub SmallVec<[Float; 4]>);
impl ImageChannelValues {
pub fn average(&self) -> Float {
if self.0.is_empty() {
return 0.0;
}
let sum: Float = self.0.iter().sum();
sum / (self.0.len() as Float)
}
pub fn max_value(&self) -> Float {
self.0.iter().fold(Float::MIN, |a, &b| a.max(b))
}
}
impl From<&[Float]> for ImageChannelValues {
fn from(slice: &[Float]) -> Self {
Self(SmallVec::from_slice(slice))
}
}
impl From<Vec<Float>> for ImageChannelValues {
fn from(vec: Vec<Float>) -> Self {
Self(SmallVec::from_vec(vec))
}
}
impl<const N: usize> From<[Float; N]> for ImageChannelValues {
fn from(arr: [Float; N]) -> Self {
Self(SmallVec::from_slice(&arr))
}
}
impl Deref for ImageChannelValues {
type Target = SmallVec<[Float; 4]>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ImageChannelValues {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WrapMode {
Black,
Clamp,
Repeat,
OctahedralSphere,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct WrapMode2D {
pub uv: [WrapMode; 2],
}
impl From<WrapMode> for WrapMode2D {
fn from(w: WrapMode) -> Self {
Self { uv: [w, w] }
}
}
#[derive(Debug, Clone, Default)]
pub struct ImageChannelDesc {
pub offset: Vec<usize>,
}
impl ImageChannelDesc {
pub fn new(offset: &[usize]) -> Self {
Self {
offset: offset.into(),
}
}
pub fn size(&self) -> usize {
self.offset.len()
}
pub fn is_empty(&self) -> bool {
self.offset.is_empty()
}
pub fn is_identity(&self) -> bool {
for i in 0..self.size() {
if self.offset[i] != i {
return false;
}
}
true
}
}
#[derive(Debug, Default)]
pub struct ImageMetadata {
pub render_time_seconds: Option<Float>,
pub camera_from_world: Option<SquareMatrix<Float, 4>>,
pub ndc_from_world: Option<SquareMatrix<Float, 4>>,
pub pixel_bounds: Option<Bounds2i>,
pub full_resolution: Option<Point2i>,
pub samples_per_pixel: Option<i32>,
pub mse: Option<Float>,
pub colorspace: Option<RGBColorSpace>,
pub strings: HashMap<String, String>,
pub string_vectors: HashMap<String, Vec<String>>,
}

View file

@ -1,443 +0,0 @@
pub mod metadata;
pub mod ops;
pub mod pixel;
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::core::pbrt::Float;
use crate::spectra::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::utils::containers::Array2D;
use crate::utils::math::{lerp, square};
use core::hash;
use half::f16;
use pixel::PixelStorage;
use rayon::prelude::*;
use smallvec::{SmallVec, smallvec};
use std::ops::{Deref, DerefMut};
pub use metadata::{ImageChannelDesc, ImageChannelValues, ImageMetadata, WrapMode, WrapMode2D};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PixelFormat {
U8,
F16,
F32,
}
impl PixelFormat {
pub fn is_8bit(&self) -> bool {
matches!(self, PixelFormat::U8)
}
pub fn is_16bit(&self) -> bool {
matches!(self, PixelFormat::F16)
}
pub fn is_32bit(&self) -> bool {
matches!(self, PixelFormat::F32)
}
pub fn texel_bytes(&self) -> usize {
match self {
PixelFormat::U8 => 1,
PixelFormat::F16 => 2,
PixelFormat::F32 => 4,
}
}
}
impl std::fmt::Display for PixelFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PixelFormat::U8 => write!(f, "U256"),
PixelFormat::F16 => write!(f, "Half"),
PixelFormat::F32 => write!(f, "Float"),
}
}
}
#[derive(Debug, Clone)]
pub enum PixelData {
U8(Vec<u8>),
F16(Vec<f16>),
F32(Vec<f32>),
}
#[derive(Debug, Clone)]
pub struct Image {
pub format: PixelFormat,
pub resolution: Point2i,
pub channel_names: Vec<String>,
pub encoding: ColorEncoding,
pub pixels: PixelData,
}
#[derive(Debug)]
pub struct ImageAndMetadata {
pub image: Image,
pub metadata: ImageMetadata,
}
impl Image {
fn from_vector(
format: PixelFormat,
resolution: Point2i,
channel_names: Vec<String>,
encoding: ColorEncoding,
) -> Self {
let size = (resolution.x() * resolution.y()) as usize * channel_names.len();
let pixels = match format {
PixelFormat::U8 => PixelData::U8(vec![0; size]),
PixelFormat::F16 => PixelData::F16(vec![f16::ZERO; size]),
PixelFormat::F32 => PixelData::F32(vec![0.0; size]),
};
Self {
format,
resolution,
channel_names,
encoding,
pixels,
}
}
pub fn new(
format: PixelFormat,
resolution: Point2i,
channel_names: &[&str],
encoding: ColorEncoding,
) -> Self {
let owned_names = channel_names.iter().map(|s| s.to_string()).collect();
Self::from_vector(format, resolution, owned_names, encoding)
}
pub fn format(&self) -> PixelFormat {
self.format
}
pub fn resolution(&self) -> Point2i {
self.resolution
}
pub fn n_channels(&self) -> usize {
self.channel_names.len()
}
pub fn channel_names(&self) -> Vec<&str> {
self.channel_names.iter().map(|s| s.as_str()).collect()
}
pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> {
desc.offset
.iter()
.map(|&i| self.channel_names[i].as_str())
.collect()
}
pub fn encoding(&self) -> ColorEncoding {
self.encoding
}
pub fn pixel_offset(&self, p: Point2i) -> usize {
(p.y() as usize * self.resolution.x() as usize + p.x() as usize) * self.n_channels()
}
pub fn get_channel(&self, p: Point2i, c: usize) -> Float {
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn get_channel_with_wrap(&self, p: Point2i, c: usize, wrap: WrapMode2D) -> Float {
let mut pp = p;
if !self.remap_pixel_coords(&mut pp, wrap) {
return 0.0;
}
let idx = self.pixel_offset(pp) + c;
match &self.pixels {
PixelData::U8(d) => u8::to_linear(d[idx], self.encoding),
PixelData::F16(d) => f16::to_linear(d[idx], self.encoding),
PixelData::F32(d) => f32::to_linear(d[idx], self.encoding),
}
}
pub fn get_channels(&self, p: Point2i, wrap: WrapMode2D) -> ImageChannelValues {
let mut pp = p;
if !self.remap_pixel_coords(&mut pp, wrap) {
return ImageChannelValues(smallvec![0.0; self.n_channels()]);
}
let start_idx = self.pixel_offset(pp);
let n_channels = self.n_channels();
let mut values: SmallVec<[Float; 4]> = SmallVec::with_capacity(n_channels);
match &self.pixels {
PixelData::U8(data) => {
let slice = &data[start_idx..start_idx + n_channels];
for &v in slice {
values.push(u8::to_linear(v, self.encoding));
}
}
PixelData::F16(data) => {
let slice = &data[start_idx..start_idx + n_channels];
for &v in slice {
values.push(f16::to_linear(v, self.encoding));
}
}
PixelData::F32(data) => {
let slice = &data[start_idx..start_idx + n_channels];
for &v in slice {
values.push(f32::to_linear(v, self.encoding));
}
}
}
ImageChannelValues(values)
}
pub fn get_channels_desc(
&self,
p: Point2i,
desc: &ImageChannelDesc,
wrap: WrapMode2D,
) -> ImageChannelValues {
let mut pp = p;
if !self.remap_pixel_coords(&mut pp, wrap) {
return ImageChannelValues(smallvec![0.0; desc.offset.len()]);
}
let pixel_offset = self.pixel_offset(pp);
let mut values: SmallVec<[Float; 4]> = SmallVec::with_capacity(desc.offset.len());
match &self.pixels {
PixelData::U8(data) => {
for &channel_idx in &desc.offset {
let val = data[pixel_offset + channel_idx];
values.push(u8::to_linear(val, self.encoding));
}
}
PixelData::F16(data) => {
for &channel_idx in &desc.offset {
let val = data[pixel_offset + channel_idx];
values.push(f16::to_linear(val, self.encoding));
}
}
PixelData::F32(data) => {
for &channel_idx in &desc.offset {
let val = data[pixel_offset + channel_idx];
values.push(f32::to_linear(val, self.encoding));
}
}
}
ImageChannelValues(values)
}
pub fn get_channels_default(&self, p: Point2i) -> ImageChannelValues {
self.get_channels(p, WrapMode::Clamp.into())
}
pub fn all_channels_desc(&self) -> ImageChannelDesc {
ImageChannelDesc {
offset: (0..self.n_channels()).collect(),
}
}
pub fn get_channel_desc(
&self,
requested_channels: &[&str],
) -> Result<ImageChannelDesc, String> {
let mut offset = Vec::with_capacity(requested_channels.len());
for &req in requested_channels.iter() {
match self.channel_names.iter().position(|n| n == req) {
Some(idx) => {
offset.push(idx);
}
None => {
return Err(format!(
"Image is missing requested channel '{}'. Available channels: {:?}",
req, self.channel_names
));
}
}
}
Ok(ImageChannelDesc { offset })
}
pub fn set_channel(&mut self, p: Point2i, c: usize, value: Float) {
let val_no_nan = if value.is_nan() { 0.0 } else { value };
let offset = self.pixel_offset(p) + c;
match &mut self.pixels {
PixelData::U8(data) => {
let linear = [val_no_nan];
self.encoding
.from_linear_slice(&linear, &mut data[offset..offset + 1]);
}
PixelData::F16(data) => data[offset] = f16::from_f32(val_no_nan),
PixelData::F32(data) => data[offset] = val_no_nan,
}
}
pub fn set_channels(
&mut self,
p: Point2i,
desc: &ImageChannelDesc,
values: &ImageChannelValues,
) {
assert_eq!(desc.size(), values.len());
for i in 0..desc.size() {
self.set_channel(p, desc.offset[i], values[i]);
}
}
pub fn set_channels_all(&mut self, p: Point2i, values: &ImageChannelValues) {
self.set_channels(p, &self.all_channels_desc(), values)
}
fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool {
for i in 0..2 {
if p[i] >= 0 && p[i] < self.resolution[i] {
continue;
}
match wrap_mode.uv[i] {
WrapMode::Black => return false,
WrapMode::Clamp => p[i] = p[i].clamp(0, self.resolution[i] - 1),
WrapMode::Repeat => p[i] = p[i].rem_euclid(self.resolution[i]),
WrapMode::OctahedralSphere => {
p[i] = p[i].clamp(0, self.resolution[i] - 1);
}
}
}
true
}
pub fn bilerp_channel(&self, p: Point2f, c: usize) -> Float {
self.bilerp_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: usize, wrap_mode: WrapMode2D) -> Float {
let x = p.x() * self.resolution.x() as Float - 0.5;
let y = p.y() * self.resolution.y() as Float - 0.5;
let xi = x.floor() as i32;
let yi = y.floor() as i32;
let dx = x - xi as Float;
let dy = y - yi as Float;
let v00 = self.get_channel_with_wrap(Point2i::new(xi, yi), c, wrap_mode);
let v10 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi), c, wrap_mode);
let v01 = self.get_channel_with_wrap(Point2i::new(xi, yi + 1), c, wrap_mode);
let v11 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi + 1), c, wrap_mode);
lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11))
}
pub fn lookup_nearest_channel_with_wrap(
&self,
p: Point2f,
c: usize,
wrap_mode: WrapMode2D,
) -> Float {
let pi = Point2i::new(
p.x() as i32 * self.resolution.x(),
p.y() as i32 * self.resolution.y(),
);
self.get_channel_with_wrap(pi, c, wrap_mode)
}
pub fn lookup_nearest_channel(&self, p: Point2f, c: usize) -> Float {
self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
}
pub fn get_sampling_distribution<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float>
where
F: Fn(Point2f) -> Float + Sync + Send,
{
let width = self.resolution.x();
let height = self.resolution.y();
let mut dist = Array2D::new_with_dims(width as usize, height as usize);
dist.values
.par_chunks_mut(width as usize)
.enumerate()
.for_each(|(y, row)| {
let y = y as i32;
for (x, out_val) in row.iter_mut().enumerate() {
let x = x as i32;
let value = self.get_channels_default(Point2i::new(x, y)).average();
let u = (x as Float + 0.5) / width as Float;
let v = (y as Float + 0.5) / height as Float;
let p = domain.lerp(Point2f::new(u, v));
*out_val = value * dxd_a(p);
}
});
dist
}
pub fn get_sampling_distribution_uniform(&self) -> Array2D<Float> {
let default_domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
self.get_sampling_distribution(|_| 1.0, default_domain)
}
pub fn mse(
&self,
desc: ImageChannelDesc,
ref_img: &Image,
generate_mse_image: bool,
) -> (ImageChannelValues, Option<Image>) {
let mut sum_se: Vec<f64> = vec![0.; desc.size()];
let names_ref = self.channel_names_from_desc(&desc);
let ref_desc = ref_img
.get_channel_desc(&self.channel_names_from_desc(&desc))
.expect("Channels not found in image");
assert_eq!(self.resolution(), ref_img.resolution());
let width = self.resolution.x() as usize;
let height = self.resolution.y() as usize;
let n_channels = desc.offset.len();
let mut mse_pixels = if generate_mse_image {
vec![0.0f32; width * height * n_channels]
} else {
Vec::new()
};
for y in 0..self.resolution().y() {
for x in 0..self.resolution().x() {
let v = self.get_channels_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into());
let v_ref =
self.get_channels_desc(Point2i::new(x, y), &ref_desc, WrapMode::Clamp.into());
for c in 0..desc.size() {
let se = square(v[c] as f64 - v_ref[c] as f64);
if se.is_infinite() {
continue;
}
sum_se[c] += se;
if generate_mse_image {
let idx = (y as usize * width + x as usize) * n_channels + c;
mse_pixels[idx] = se as f32;
}
}
}
}
let pixel_count = (self.resolution.x() * self.resolution.y()) as f64;
let mse_values: SmallVec<[Float; 4]> =
sum_se.iter().map(|&s| (s / pixel_count) as Float).collect();
let mse_image = if generate_mse_image {
Some(Image::new(
PixelFormat::F32,
self.resolution,
&names_ref,
LINEAR,
))
} else {
None
};
(ImageChannelValues(mse_values), mse_image)
}
}

View file

@ -2,16 +2,16 @@
#![feature(float_erf)] #![feature(float_erf)]
#![feature(f16)] #![feature(f16)]
mod cameras; pub mod bxdfs;
mod core; pub mod cameras;
mod data; pub mod core;
mod filters; pub mod data;
mod images; pub mod filters;
mod integrators; pub mod lights;
mod lights; pub mod materials;
mod shapes; pub mod shapes;
mod spectra; pub mod spectra;
mod textures; pub mod textures;
mod utils; pub mod utils;
pub use core::pbrt::*; pub use core::pbrt::*;

View file

@ -1,114 +1,55 @@
use crate::PI; use crate::PI;
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::core::geometry::*; use crate::core::geometry::*;
use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction}; use crate::core::image::Image;
use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
};
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::shape::{Shape, ShapeSampleContext, ShapeTrait};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, TextureEvalContext, UniversalTextureEvaluator}; use crate::core::texture::{
use crate::images::Image; GPUFloatTexture, TextureEvalContext, TextureEvaluator, UniversalTextureEvaluator,
use crate::shapes::{Shape, ShapeSampleContext}; };
use crate::spectra::*; use crate::spectra::*;
use crate::utils::Transform;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::{Ptr, Transform};
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct DiffuseAreaLight { pub struct DiffuseAreaLight {
pub base: LightBase, pub base: LightBase,
pub shape: *const Shape, pub shape: Ptr<Shape>,
pub alpha: *const GPUFloatTexture, pub alpha: Ptr<GPUFloatTexture>,
pub image_color_space: Ptr<RGBColorSpace>,
pub lemit: Ptr<DenselySampledSpectrum>,
pub image: Ptr<Image>,
pub area: Float, pub area: Float,
pub two_sided: bool, pub two_sided: bool,
pub lemit: DenselySampledSpectrum,
pub scale: Float, pub scale: Float,
pub image: *const Image,
pub image_color_space: RGBColorSpace,
} }
#[cfg(not(target_os = "cuda"))] unsafe impl Send for DiffuseAreaLight {}
unsafe impl Sync for DiffuseAreaLight {}
impl DiffuseAreaLight { impl DiffuseAreaLight {
#[allow(clippy::too_many_arguments)]
pub fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
le: Spectrum,
scale: Float,
shape: Shape,
alpha: *const GPUFloatTexture,
image: *const Image,
image_color_space: *const RGBColorSpace,
two_sided: bool,
) -> Self {
let is_constant_zero = match &alpha {
GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
_ => false,
};
let (light_type, stored_alpha) = if is_constant_zero {
(LightType::DeltaPosition, None)
} else {
(LightType::Area, Some(alpha))
};
let base = LightBase::new(light_type, &render_from_light, &medium_interface);
let lemit = LightBase::lookup_spectrum(&le);
if let Some(im) = &image {
let desc = im
.get_channel_desc(&["R", "G", "B"])
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
assert_eq!(3, desc.size(), "Image channel description size mismatch");
assert!(
desc.is_identity(),
"Image channel description is not identity"
);
assert!(
image_color_space.is_some(),
"Image provided but ColorSpace is missing"
);
}
let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
println!(
"Scaling detected in rendering to light space transformation! \
The system has numerous assumptions, implicit and explicit, \
that this transform will have no scale factors in it. \
Proceed at your own risk; your image may have errors."
);
}
Self {
base,
area: shape.area(),
shape,
alpha: stored_alpha,
two_sided,
lemit,
scale,
image,
image_color_space,
}
}
fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if !self.two_sided && n.dot(wo) <= 0.0 { if !self.two_sided && n.dot(wo.into()) <= 0.0 {
return SampledSpectrum::new(0.0); return SampledSpectrum::new(0.0);
} }
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs); self.lemit.sample(lambda) * self.scale
spec.sample(lambda) * self.scale
} }
fn alpha_masked(&self, intr: &Interaction) -> bool { fn alpha_masked(&self, intr: &Interaction) -> bool {
let Some(alpha_tex) = &self.alpha else { if self.alpha.is_null() {
return false; return false;
}; };
let ctx = TextureEvalContext::from(intr); let ctx = TextureEvalContext::from(intr);
let a = UniversalTextureEvaluator.evaluate_float(alpha_tex, &ctx); let a = UniversalTextureEvaluator.evaluate_float(&*self.alpha, &ctx);
if a >= 1.0 { if a >= 1.0 {
return false; return false;
} }
@ -133,15 +74,14 @@ impl LightTrait for DiffuseAreaLight {
) -> Option<LightLiSample> { ) -> Option<LightLiSample> {
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0); let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
let ss = self.shape.sample_from_context(&shape_ctx, u)?; let ss = self.shape.sample_from_context(&shape_ctx, u)?;
let mut intr: SurfaceInteraction = ss.intr.as_ref().clone(); let mut intr = ss.intr;
intr.set_medium_interface(self.base.medium_interface);
intr.common.medium_interface = Some(self.base.medium_interface.clone());
let p = intr.p(); let p = intr.p();
let n = intr.n(); let n = intr.n();
let uv = intr.uv; let uv = intr.get_common().uv;
let generic_intr = Interaction::Surface(intr); // let generic_intr = Interaction::Surface(intr);
if self.alpha_masked(&generic_intr) { if self.alpha_masked(&intr) {
return None; return None;
} }
@ -152,7 +92,7 @@ impl LightTrait for DiffuseAreaLight {
return None; return None;
} }
Some(LightLiSample::new(le, wi, ss.pdf, generic_intr)) Some(LightLiSample::new(le, wi, ss.pdf, intr))
} }
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float { fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
@ -179,16 +119,16 @@ impl LightTrait for DiffuseAreaLight {
if self.alpha_masked(&intr) { if self.alpha_masked(&intr) {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
if let Some(image) = &self.image { if !self.image.is_null() {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
uv[1] = 1. - uv[1]; uv[1] = 1. - uv[1];
for c in 0..3 { for c in 0..3 {
rgb[c] = image.bilerp_channel(uv, c); rgb[c] = self.image.bilerp_channel(uv, c as i32);
} }
let spec = RGBIlluminantSpectrum::new( let spec = RGBIlluminantSpectrum::new(
self.image_color_space.as_ref().unwrap(), self.image_color_space.as_ref().unwrap(),
RGB::clamp_zero(rgb), rgb.clamp_zero(),
); );
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
@ -204,21 +144,21 @@ impl LightTrait for DiffuseAreaLight {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut l = SampledSpectrum::new(0.); let mut l = SampledSpectrum::new(0.);
if let Some(image) = &self.image { if !self.image.is_null() {
for y in 0..image.resolution().y() { for y in 0..self.image.resolution().y() {
for x in 0..image.resolution().x() { for x in 0..self.image.resolution().x() {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = image.get_channel(Point2i::new(x, y), c); rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
} }
l += RGBIlluminantSpectrum::new( l += RGBIlluminantSpectrum::new(
self.image_color_space.as_ref().unwrap(), self.image_color_space.as_ref().unwrap(),
RGB::clamp_zero(rgb), rgb.clamp_zero(),
) )
.sample(&lambda); .sample(&lambda);
} }
} }
l *= self.scale / (image.resolution().x() * image.resolution().y()) as Float; l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float;
} else { } else {
l = self.lemit.sample(&lambda) * self.scale; l = self.lemit.sample(&lambda) * self.scale;
} }
@ -234,11 +174,11 @@ impl LightTrait for DiffuseAreaLight {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let mut phi = 0.; let mut phi = 0.;
if let Some(image) = &self.image { if !self.image.is_null() {
for y in 0..image.resolution.y() { for y in 0..self.image.resolution.y() {
for x in 0..image.resolution.x() { for x in 0..self.image.resolution.x() {
for c in 0..3 { for c in 0..3 {
phi += image.get_channel(Point2i::new(x, y), c); phi += self.image.get_channel(Point2i::new(x, y), c);
} }
} }
} }

View file

@ -1,14 +1,18 @@
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f}; use crate::core::geometry::{
use crate::core::interaction::{Interaction, InteractionData}; Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait}; use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::{ArenaPtr, Ptr};
use crate::{Float, PI}; use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DistantLight { pub struct DistantLight {
pub base: LightBase, pub base: LightBase,
pub lemit_coeffs: [Float; 32], pub lemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
@ -26,8 +30,7 @@ impl DistantLight {
.apply_to_vector(Vector3f::new(0., 0., 1.)) .apply_to_vector(Vector3f::new(0., 0., 1.))
.normalize(); .normalize();
let p_outside = ctx_p + wi * 2. * self.scene_radius; let p_outside = ctx_p + wi * 2. * self.scene_radius;
let spectrum = DenselySampledSpectrum::from_array(&self.lemit_coeffs); let li = self.scale * self.lemit.sample(lambda);
let li = self.scale * spectrum.sample(lambda);
(li, wi, 1.0, p_outside) (li, wi, 1.0, p_outside)
} }
} }
@ -56,11 +59,8 @@ impl LightTrait for DistantLight {
let p_outside = ctx.p() + wi * 2. * self.scene_radius; let p_outside = ctx.p() + wi * 2. * self.scene_radius;
let li = self.scale * self.lemit.sample(lambda); let li = self.scale * self.lemit.sample(lambda);
let intr = SimpleInteraction::new( let base = InteractionBase::new_boundary(p_outside, 0.0, self.base.medium_interface);
Point3fi::new_from_point(p_outside), let intr = SimpleInteraction::new(base);
0.0,
Some(self.base.medium_interface.clone()),
);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }

View file

@ -1,46 +1,26 @@
use crate::Float; use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f};
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Vector3f}; use crate::core::image::Image;
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightTrait}; use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::utils::Transform; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::math::equal_area_sphere_to_square;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Ptr, Transform};
use crate::{Float, PI};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GoniometricLight { pub struct GoniometricLight {
pub base: LightBase, pub base: LightBase,
iemit: DenselySampledSpectrum, iemit: DenselySampledSpectrum,
scale: Float, scale: Float,
image: Image, image: Ptr<Image>,
distrib: PiecewiseConstant2D, distrib: Ptr<PiecewiseConstant2D>,
} }
impl GoniometricLight { impl GoniometricLight {
pub fn new(
render_from_light: &Transform,
medium_interface: &MediumInterface,
iemit: Spectrum,
scale: Float,
image: Image,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
render_from_light,
medium_interface,
);
let i_interned = LightBase::lookup_spectrum(&iemit);
let d = image.get_sampling_distribution_uniform();
let distrib = PiecewiseConstant2D::new_with_data(&d);
Self {
base,
iemit: i_interned,
scale,
image,
distrib,
}
}
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
let uv = equal_area_sphere_to_square(w); let uv = equal_area_sphere_to_square(w);
self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0) self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0)

View file

@ -1,9 +1,11 @@
use crate::{ use crate::{
core::geometry::Frame, core::{
core::medium::Medium, geometry::{Frame, VectorLike},
spectra::{RGB, RGBColorSpace, RGBIlluminantSpectrum}, interaction::InteractionBase,
},
spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{ utils::{
math::{clamp, equal_area_square_to_sphere}, math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square},
sampling::{ sampling::{
AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere, AliasTable, PiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
uniform_sphere_pdf, uniform_sphere_pdf,
@ -11,36 +13,35 @@ use crate::{
}, },
}; };
use crate::images::{PixelFormat, WrapMode}; use crate::core::color::RGB;
use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f,
};
use crate::core::image::{Image, PixelFormat, WrapMode};
use crate::core::interaction::{Interaction, SimpleInteraction};
use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::{Medium, MediumInterface};
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Transform;
use crate::utils::ptr::Ptr;
use crate::{Float, PI};
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct InfiniteUniformLight { pub struct InfiniteUniformLight {
pub base: LightBase, pub base: LightBase,
pub lemit: u32, pub lemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
} }
#[cfg(not(target_os = "cuda"))] unsafe impl Send for InfiniteUniformLight {}
impl InfiniteUniformLight { unsafe impl Sync for InfiniteUniformLight {}
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
let base = LightBase::new(
LightType::Infinite,
&render_from_light,
&MediumInterface::default(),
);
let lemit = LightBase::lookup_spectrum(&le);
Self {
base,
lemit,
scale,
scene_center: Point3f::default(),
scene_radius: 0.,
}
}
}
impl LightTrait for InfiniteUniformLight { impl LightTrait for InfiniteUniformLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
@ -58,10 +59,12 @@ impl LightTrait for InfiniteUniformLight {
} }
let wi = sample_uniform_sphere(u); let wi = sample_uniform_sphere(u);
let pdf = uniform_sphere_pdf(); let pdf = uniform_sphere_pdf();
let intr_simple = SimpleInteraction::new_interface( let base = InteractionBase::new_boundary(
ctx.p() + wi * (2. * self.scene_radius), ctx.p() + wi * (2. * self.scene_radius),
Some(MediumInterface::default()), 0.,
MediumInterface::default(),
); );
let intr_simple = SimpleInteraction::new(base);
let intr = Interaction::Simple(intr_simple); let intr = Interaction::Simple(intr_simple);
Some(LightLiSample::new( Some(LightLiSample::new(
@ -98,102 +101,50 @@ impl LightTrait for InfiniteUniformLight {
fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum { fn le(&self, _ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum {
self.scale * self.lemit.sample(lambda) self.scale * self.lemit.sample(lambda)
} }
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) { fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!() todo!()
} }
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
todo!() todo!()
} }
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda) 4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct InfiniteImageLight { pub struct InfiniteImageLight {
base: LightBase, pub base: LightBase,
image: Image, pub image: Ptr<Image>,
image_color_space: u32, pub image_color_space: Ptr<RGBColorSpace>,
scale: Float, pub distrib: Ptr<PiecewiseConstant2D>,
scene_radius: Float, pub compensated_distrib: Ptr<PiecewiseConstant2D>,
scene_center: Point3f, pub scale: Float,
distrib: PiecewiseConstant2D, pub scene_radius: Float,
compensated_distrib: PiecewiseConstant2D, pub scene_center: Point3f,
} }
#[cfg(not(target_os = "cuda"))] unsafe impl Send for InfiniteImageLight {}
unsafe impl Sync for InfiniteImageLight {}
impl InfiniteImageLight { impl InfiniteImageLight {
pub fn new(
render_from_light: TransformGeneric<Float>,
image: Image,
image_color_space: Arc<RGBColorSpace>,
scale: Float,
filename: String,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
&render_from_light,
&MediumInterface::default(),
);
let desc = image
.get_channel_desc(&["R", "G", "B"])
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
assert_eq!(3, desc.size());
assert!(desc.is_identity());
if image.resolution().x() != image.resolution().y() {
panic!(
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
filename,
image.resolution.x(),
image.resolution.y()
);
}
let mut d = image.get_sampling_distribution_uniform();
let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.));
let distrib = PiecewiseConstant2D::new_with_bounds(&d, domain);
let slice = &mut d.values; // or d.as_slice_mut()
let count = slice.len() as Float;
let sum: Float = slice.iter().sum();
let average = sum / count;
for v in slice.iter_mut() {
*v = (*v - average).max(0.0);
}
let all_zero = slice.iter().all(|&v| v == 0.0);
if all_zero {
for v in slice.iter_mut() {
*v = 1.0;
}
}
let compensated_distrib = PiecewiseConstant2D::new_with_bounds(&d, domain);
Self {
base,
image,
image_color_space,
scene_center: Point3f::default(),
scene_radius: 0.,
scale,
distrib,
compensated_distrib,
}
}
fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel_with_wrap( rgb[c] = self.image.lookup_nearest_channel_with_wrap(
uv, uv,
c, c as i32,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
} }
let spec = let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }
} }
@ -225,13 +176,14 @@ impl LightTrait for InfiniteImageLight {
let pdf = map_pdf / (4. * PI); let pdf = map_pdf / (4. * PI);
// Return radiance value for infinite light direction // Return radiance value for infinite light direction
let mut simple_intr = SimpleInteraction::new_interface( let base = InteractionBase::new_boundary(
ctx.p() + wi * (2. * self.scene_radius), ctx.p() + wi * (2. * self.scene_radius),
Some(MediumInterface::default()), 0.,
self.base.medium_interface,
); );
let simple_intr = SimpleInteraction::new(base);
simple_intr.common.medium_interface = Some(self.base.medium_interface.clone());
let intr = Interaction::Simple(simple_intr); let intr = Interaction::Simple(simple_intr);
Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr)) Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr))
} }
@ -278,14 +230,11 @@ impl LightTrait for InfiniteImageLight {
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.get_channel_with_wrap( rgb[c] = self.image.get_channel_with_wrap(
Point2i::new(u, v), Point2i::new(u, v),
c, c as i32,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
} }
sum_l += RGBIlluminantSpectrum::new( sum_l += RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero())
self.image_color_space.as_ref(),
RGB::clamp_zero(rgb),
)
.sample(&lambda); .sample(&lambda);
} }
} }
@ -309,10 +258,9 @@ impl LightTrait for InfiniteImageLight {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct InfinitePortalLight { pub struct InfinitePortalLight {
pub base: LightBase, pub base: LightBase,
pub image: Image, pub image: Ptr<Image>,
pub image_color_space: RGBColorSpace, pub image_color_space: Ptr<RGBColorSpace>,
pub scale: Float, pub scale: Float,
pub filename: String,
pub portal: [Point3f; 4], pub portal: [Point3f; 4],
pub portal_frame: Frame, pub portal_frame: Frame,
pub distribution: WindowedPiecewiseConstant2D, pub distribution: WindowedPiecewiseConstant2D,
@ -320,144 +268,13 @@ pub struct InfinitePortalLight {
pub scene_radius: Float, pub scene_radius: Float,
} }
#[cfg(not(target_os = "cuda"))]
impl InfinitePortalLight { impl InfinitePortalLight {
pub fn new(
render_from_light: TransformGeneric<Float>,
equal_area_image: &Image,
image_color_space: Arc<RGBColorSpace>,
scale: Float,
filename: String,
points: Vec<Point3f>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
&render_from_light,
&MediumInterface::default(),
);
let desc = equal_area_image
.get_channel_desc(&["R", "G", "B"])
.unwrap_or_else(|_| {
panic!(
"{}: image used for PortalImageInfiniteLight doesn't have R, G, B channels.",
filename
)
});
assert_eq!(3, desc.offset.len());
let src_res = equal_area_image.resolution;
if src_res.x() != src_res.y() {
panic!(
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
filename,
src_res.x(),
src_res.y()
);
}
if points.len() != 4 {
panic!(
"Expected 4 vertices for infinite light portal but given {}",
points.len()
);
}
let portal: [Point3f; 4] = [points[0], points[1], points[2], points[3]];
let p01 = (portal[1] - portal[0]).normalize();
let p12 = (portal[2] - portal[1]).normalize();
let p32 = (portal[2] - portal[3]).normalize();
let p03 = (portal[3] - portal[0]).normalize();
if (p01.dot(p32) - 1.0).abs() > 0.001 || (p12.dot(p03) - 1.0).abs() > 0.001 {
panic!("Infinite light portal isn't a planar quadrilateral (opposite edges)");
}
if p01.dot(p12).abs() > 0.001
|| p12.dot(p32).abs() > 0.001
|| p32.dot(p03).abs() > 0.001
|| p03.dot(p01).abs() > 0.001
{
panic!("Infinite light portal isn't a planar quadrilateral (perpendicular edges)");
}
let portal_frame = Frame::from_xy(p03, p01);
let width = src_res.x();
let height = src_res.y();
let mut new_pixels = vec![0.0 as Float; (width * height * 3) as usize];
new_pixels
.par_chunks_mut((width * 3) as usize)
.enumerate()
.for_each(|(y, row_pixels)| {
let y = y as i32;
for x in 0..width {
let uv = Point2f::new(
(x as Float + 0.5) / width as Float,
(y as Float + 0.5) / height as Float,
);
let (w_world, _) = Self::render_from_image(portal_frame, uv);
let w_local = render_from_light.apply_inverse_vector(w_world).normalize();
let uv_equi = equal_area_sphere_to_square(w_local);
let pixel_idx = (x * 3) as usize;
for c in 0..3 {
let val = equal_area_image.bilerp_channel_with_wrap(
uv_equi,
c,
WrapMode::OctahedralSphere.into(),
);
row_pixels[pixel_idx + c] = val;
}
}
});
let image = Image::new(
PixelFormat::F32,
src_res,
&["R", "G", "B"],
equal_area_image.encoding,
);
let duv_dw_closure = |p: Point2f| -> Float {
let (_, jacobian) = Self::render_from_image(portal_frame, p);
jacobian
};
let d = image.get_sampling_distribution(
duv_dw_closure,
Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)),
);
let distribution = WindowedPiecewiseConstant2D::new(d);
Self {
base,
image,
image_color_space,
scale,
scene_center: Point3f::default(),
scene_radius: 0.,
filename,
portal,
portal_frame,
distribution,
}
}
pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c) rgb[c] = self.image.lookup_nearest_channel(uv, c as i32)
} }
let spec = let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }
@ -529,7 +346,8 @@ impl LightTrait for InfinitePortalLight {
let pdf = map_pdf / duv_dw; let pdf = map_pdf / duv_dw;
let l = self.image_lookup(uv, lambda); let l = self.image_lookup(uv, lambda);
let pl = ctx.p() + 2. * self.scene_radius * wi; let pl = ctx.p() + 2. * self.scene_radius * wi;
let sintr = SimpleInteraction::new_interface(pl, Some(self.base.medium_interface.clone())); let base = InteractionBase::new_boundary(pl, 0., self.base.medium_interface);
let sintr = SimpleInteraction::new(base);
let intr = Interaction::Simple(sintr); let intr = Interaction::Simple(sintr);
Some(LightLiSample::new(l, wi, pdf, intr)) Some(LightLiSample::new(l, wi, pdf, intr))
} }
@ -571,8 +389,8 @@ impl LightTrait for InfinitePortalLight {
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) { fn preprocess(&mut self, scene_bounds: &Bounds3f) {
todo!() (self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere();
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]

View file

@ -13,3 +13,4 @@ pub use goniometric::GoniometricLight;
pub use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight}; pub use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
pub use point::PointLight; pub use point::PointLight;
pub use projection::ProjectionLight; pub use projection::ProjectionLight;
pub use spot::SpotLight;

View file

@ -1,33 +1,24 @@
use crate::Float; use crate::core::geometry::{
use crate::core::light::LightBaseData; Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
use crate::spectra::SampledSpectrum; };
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction};
use crate::core::light::{
Light, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::ptr::Ptr;
use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct PointLight { pub struct PointLight {
pub base: LightBasea, pub base: LightBase,
pub i:
pub scale: Float, pub scale: Float,
pub i: Ptr<DenselySampledSpectrum>,
} }
impl LightTrait for PointLight { impl LightTrait for PointLight {
fn sample_li_base(
&self,
ctx_p: Point3f,
lambda: &SampledWavelengths,
) -> (SampledSpectrum, Vector3f, Float, Point3fi) {
let pi = self
.base
.render_from_light
.apply_to_interval(&Point3fi::default());
let p: Point3f = pi.into();
let wi = (p - ctx_p).normalize();
let spectrum = DenselySampledSpectrum::from_array(&self.i_coeffs);
let li = self.scale * spectrum.sample(lambda) / p.distance_squared(ctx_p);
(li, wi, 1.0, pi)
}
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
@ -46,7 +37,8 @@ impl LightTrait for PointLight {
let p: Point3f = pi.into(); let p: Point3f = pi.into();
let wi = (p - ctx.p()).normalize(); let wi = (p - ctx.p()).normalize();
let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p()); let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p());
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone())); let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }

View file

@ -1,5 +1,19 @@
use crate::Float;
use crate::core::color::RGB;
use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta,
};
use crate::core::image::Image;
use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface;
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{radians, square};
use crate::utils::ptr::Ptr;
use crate::{ use crate::{
spectra::{RGB, RGBColorSpace}, spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{Transform, sampling::PiecewiseConstant2D}, utils::{Transform, sampling::PiecewiseConstant2D},
}; };
@ -12,66 +26,13 @@ pub struct ProjectionLight {
pub screen_bounds: Bounds2f, pub screen_bounds: Bounds2f,
pub screen_from_light: Transform, pub screen_from_light: Transform,
pub light_from_screen: Transform, pub light_from_screen: Transform,
pub image_id: u32,
pub a: Float, pub a: Float,
pub distrib: PiecewiseConstant2D, pub image: Ptr<Image>,
pub image_color_space: *const RGBColorSpace, pub distrib: Ptr<PiecewiseConstant2D>,
pub image_color_space: Ptr<RGBColorSpace>,
} }
impl ProjectionLight { impl ProjectionLight {
pub fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
image_id: u32,
image_color_space: RGBColorSpace,
scale: Float,
fov: Float,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
&render_from_light,
&medium_interface,
);
let image = Image::new();
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
let screen_bounds = if aspect > 1. {
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
} else {
Bounds2f::from_points(
Point2f::new(-1., 1. / aspect),
Point2f::new(1., 1. / aspect),
)
};
let hither = 1e-3;
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
let light_from_screen = screen_from_light.inverse();
let opposite = (radians(fov) / 2.).tan();
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
let a = 4. * square(opposite) * aspect_ratio;
let dwda = |p: Point2f| {
let w =
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
cos_theta(w.normalize()).powi(3)
};
let d = image.get_sampling_distribution(dwda, screen_bounds);
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
Self {
base,
image_id,
image_color_space,
screen_bounds,
screen_from_light,
light_from_screen,
scale,
hither,
a,
distrib,
}
}
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
if w.z() < self.hither { if w.z() < self.hither {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
@ -83,9 +44,9 @@ impl ProjectionLight {
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y()))); let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c); rgb[c] = self.image.lookup_nearest_channel(uv, c as i32);
} }
let s = RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb)); let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
self.scale * s.sample(&lambda) self.scale * s.sample(&lambda)
} }
} }
@ -146,13 +107,10 @@ impl LightTrait for ProjectionLight {
let dwda = cos_theta(w).powi(3); let dwda = cos_theta(w).powi(3);
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c); rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32);
} }
let s = RGBIlluminantSpectrum::new( let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero());
self.image_color_space.as_ref(),
RGB::clamp_zero(rgb),
);
sum += s.sample(&lambda) * dwda; sum += s.sample(&lambda) * dwda;
} }
} }

View file

@ -1,13 +1,12 @@
use crate::core::geometry::primitives::OctahedralVector; use crate::core::geometry::primitives::OctahedralVector;
use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike}; use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike};
use crate::core::geometry::{DirectionCone, Normal}; use crate::core::geometry::{DirectionCone, Normal};
use crate::utils::math::{clamp, lerp, sample_discrete}; use crate::core::light::Light;
use std::collections::HashMap;
use std::sync::Arc;
use crate::core::light::{LightBounds, LightSampleContext}; use crate::core::light::{LightBounds, LightSampleContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{clamp, lerp, sample_discrete};
use crate::utils::math::{safe_sqrt, square}; use crate::utils::math::{safe_sqrt, square};
use crate::utils::ptr::{Ptr, Slice};
use crate::utils::sampling::AliasTable; use crate::utils::sampling::AliasTable;
use crate::{Float, ONE_MINUS_EPSILON, PI}; use crate::{Float, ONE_MINUS_EPSILON, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
@ -155,22 +154,25 @@ impl CompactLightBounds {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SampledLight { pub struct SampledLight {
pub light: Arc<Light>, pub light: Ptr<Light>,
pub p: Float, pub p: Float,
} }
impl SampledLight { impl SampledLight {
pub fn new(light: Arc<Light>, p: Float) -> Self { pub fn new(light: Light, p: Float) -> Self {
Self { light, p } Self {
light: Ptr::from(&light),
p,
}
} }
} }
#[enum_dispatch] #[enum_dispatch]
pub trait LightSamplerTrait: Send + Sync + std::fmt::Debug { pub trait LightSamplerTrait {
fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option<SampledLight>; fn sample_with_context(&self, ctx: &LightSampleContext, u: Float) -> Option<SampledLight>;
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Arc<Light>) -> Float; fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float;
fn sample(&self, u: Float) -> Option<SampledLight>; fn sample(&self, u: Float) -> Option<SampledLight>;
fn pmf(&self, light: &Arc<Light>) -> Float; fn pmf(&self, light: &Light) -> Float;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -183,14 +185,18 @@ pub enum LightSampler {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct UniformLightSampler { pub struct UniformLightSampler {
lights: Vec<Arc<Light>>, lights: *const Light,
lights_len: u32,
} }
impl UniformLightSampler { impl UniformLightSampler {
pub fn new(lights: &[Arc<Light>]) -> Self { pub fn new(lights: *const Light, lights_len: u32) -> Self {
Self { Self { lights, lights_len }
lights: lights.to_vec(),
} }
#[inline(always)]
fn light(&self, idx: usize) -> Light {
unsafe { *self.lights.add(idx) }
} }
} }
@ -198,77 +204,52 @@ impl LightSamplerTrait for UniformLightSampler {
fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> { fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> {
self.sample(u) self.sample(u)
} }
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Arc<Light>) -> Float { fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float {
self.pmf(light) self.pmf(light)
} }
fn sample(&self, u: Float) -> Option<SampledLight> { fn sample(&self, u: Float) -> Option<SampledLight> {
if self.lights.is_empty() { if self.lights_len == 0 {
return None; return None;
} }
let light_index = (u as usize * self.lights.len()).min(self.lights.len() - 1); let light_index = (u as u32 * self.lights_len).min(self.lights_len - 1) as usize;
Some(SampledLight { Some(SampledLight {
light: self.lights[light_index].clone(), light: Ptr::from(&self.light(light_index)),
p: 1. / self.lights.len() as Float, p: 1. / self.lights_len as Float,
}) })
} }
fn pmf(&self, _light: &Arc<Light>) -> Float { fn pmf(&self, _light: &Light) -> Float {
if self.lights.is_empty() { if self.lights_len == 0 {
return 0.; return 0.;
} }
1. / self.lights.len() as Float 1. / self.lights_len as Float
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Alias {
pub q: Float,
pub alias: u32,
}
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct PowerLightSampler { pub struct PowerLightSampler {
lights: Vec<Arc<Light>>, pub lights: Slice<Light>,
light_to_index: HashMap<usize, usize>, pub lights_len: u32,
alias_table: AliasTable, pub alias_table: AliasTable,
} }
impl PowerLightSampler { unsafe impl Send for PowerLightSampler {}
pub fn new(lights: &[Arc<Light>]) -> Self { unsafe impl Sync for PowerLightSampler {}
if lights.is_empty() {
return Self {
lights: Vec::new(),
light_to_index: HashMap::new(),
alias_table: AliasTable::new(&[]),
};
}
let mut lights_vec = Vec::with_capacity(lights.len());
let mut light_to_index = HashMap::with_capacity(lights.len());
let mut light_power = Vec::with_capacity(lights.len());
let lambda = SampledWavelengths::sample_visible(0.5);
for (i, light) in lights.iter().enumerate() {
lights_vec.push(light.clone());
let ptr = Arc::as_ptr(light) as usize;
light_to_index.insert(ptr, i);
let phi = SampledSpectrum::safe_div(&light.phi(lambda), &lambda.pdf());
light_power.push(phi.average());
}
let alias_table = AliasTable::new(&light_power);
Self {
lights: lights_vec,
light_to_index,
alias_table,
}
}
}
impl LightSamplerTrait for PowerLightSampler { impl LightSamplerTrait for PowerLightSampler {
fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> { fn sample_with_context(&self, _ctx: &LightSampleContext, u: Float) -> Option<SampledLight> {
self.sample(u) self.sample(u)
} }
fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Arc<Light>) -> Float { fn pmf_with_context(&self, _ctx: &LightSampleContext, light: &Light) -> Float {
self.pmf(light) self.pmf(light)
} }
@ -279,24 +260,29 @@ impl LightSamplerTrait for PowerLightSampler {
let (light_index, pmf, _) = self.alias_table.sample(u); let (light_index, pmf, _) = self.alias_table.sample(u);
let light_ref = &self.lights[light_index as usize];
Some(SampledLight { Some(SampledLight {
light: self.lights[light_index].clone(), light: Ptr::from(light_ref),
p: pmf, p: pmf,
}) })
} }
fn pmf(&self, light: &Arc<Light>) -> Float { fn pmf(&self, light: &Light) -> Float {
if self.alias_table.size() == 0 { if self.lights_len == 0 {
return 0.; return 0.0;
} }
let ptr = Arc::as_ptr(light) as usize; let light_ptr = light as *const Light;
let start = self.lights.as_ptr();
if let Some(&index) = self.light_to_index.get(&ptr) { let end = unsafe { start.add(self.lights.len as usize) };
self.alias_table.pmf(index)
} else { if light_ptr >= start && light_ptr < end {
0.0 let index = unsafe { light_ptr.offset_from(start) };
return self.alias_table.pmf(index as u32);
} }
0.
} }
} }
@ -304,7 +290,6 @@ impl LightSamplerTrait for PowerLightSampler {
#[repr(C, align(32))] #[repr(C, align(32))]
pub struct LightBVHNode { pub struct LightBVHNode {
pub light_bounds: CompactLightBounds, pub light_bounds: CompactLightBounds,
// Bit 31 (MSB) : isLeaf (1 bit) // Bit 31 (MSB) : isLeaf (1 bit)
// Bits 0..31 : childOrLightIndex (31 bits) // Bits 0..31 : childOrLightIndex (31 bits)
packed_data: u32, packed_data: u32,
@ -373,14 +358,40 @@ impl LightBVHNode {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct BVHLightSampler { pub struct BVHLightSampler {
lights: Vec<Arc<Light>>, pub nodes: *const LightBVHNode,
infinite_lights: Vec<Arc<Light>>, pub lights: *const Light,
all_light_bounds: Bounds3f, pub infinite_lights: *const Light,
nodes: Vec<LightBVHNode>, pub bit_trails: *const u64,
light_to_bit_trail: HashMap<usize, usize>, pub nodes_len: u32,
pub lights_len: u32,
pub infinite_lights_len: u32,
pub all_light_bounds: Bounds3f,
} }
unsafe impl Send for BVHLightSampler {}
unsafe impl Sync for BVHLightSampler {}
impl BVHLightSampler { impl BVHLightSampler {
#[inline(always)]
fn node(&self, idx: usize) -> &LightBVHNode {
unsafe { &*self.nodes.add(idx) }
}
#[inline(always)]
fn light(&self, idx: usize) -> Light {
unsafe { *self.lights.add(idx) }
}
#[inline(always)]
fn infinite_light(&self, idx: usize) -> Light {
unsafe { *self.infinite_lights.add(idx) }
}
#[inline(always)]
fn bit_trail(&self, idx: usize) -> u64 {
unsafe { *self.bit_trails.add(idx) }
}
fn evaluate_cost(&self, b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float { fn evaluate_cost(&self, b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float {
let theta_o = b.cos_theta_o.acos(); let theta_o = b.cos_theta_o.acos();
let theta_e = b.cos_theta_e.acos(); let theta_e = b.cos_theta_e.acos();
@ -397,9 +408,9 @@ impl BVHLightSampler {
impl LightSamplerTrait for BVHLightSampler { impl LightSamplerTrait for BVHLightSampler {
fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option<SampledLight> { fn sample_with_context(&self, ctx: &LightSampleContext, mut u: Float) -> Option<SampledLight> {
let empty_nodes = if self.nodes.is_empty() { 0. } else { 1. }; let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
let inf_size = self.infinite_lights.len() as Float; let inf_size = self.infinite_lights_len as Float;
let light_size = self.lights.len() as Float; let light_size = self.lights_len as Float;
let p_inf = inf_size / (inf_size + empty_nodes); let p_inf = inf_size / (inf_size + empty_nodes);
@ -407,9 +418,10 @@ impl LightSamplerTrait for BVHLightSampler {
u /= p_inf; u /= p_inf;
let ind = (u * light_size).min(light_size - 1.) as usize; let ind = (u * light_size).min(light_size - 1.) as usize;
let pmf = p_inf / inf_size; let pmf = p_inf / inf_size;
Some(SampledLight::new(self.infinite_lights[ind].clone(), pmf)) return Some(SampledLight::new(self.infinite_light(ind), pmf));
} else { }
if self.nodes.is_empty() {
if self.nodes_len == 0 {
return None; return None;
} }
let p = ctx.p(); let p = ctx.p();
@ -419,19 +431,16 @@ impl LightSamplerTrait for BVHLightSampler {
let mut pmf = 1. - p_inf; let mut pmf = 1. - p_inf;
loop { loop {
let node = self.nodes[node_ind]; let node = self.node(node_ind);
if !node.is_leaf() { if !node.is_leaf() {
let children: [LightBVHNode; 2] = [ let child0_idx = node_ind + 1;
self.nodes[node_ind + 1], let child1_idx = node.child_or_light_index() as usize;
self.nodes[node.child_or_light_index() as usize], let child0 = self.node(child0_idx);
]; let child1 = self.node(child1_idx);
let ci: [Float; 2] = [ let ci: [Float; 2] = [
children[0] child0.light_bounds.importance(p, n, &self.all_light_bounds),
.light_bounds child1.light_bounds.importance(p, n, &self.all_light_bounds),
.importance(p, n, &self.all_light_bounds),
children[1]
.light_bounds
.importance(p, n, &self.all_light_bounds),
]; ];
if ci[0] == 0. && ci[1] == 0. { if ci[0] == 0. && ci[1] == 0. {
@ -441,81 +450,96 @@ impl LightSamplerTrait for BVHLightSampler {
let mut node_pmf: Float = 0.; let mut node_pmf: Float = 0.;
let child = sample_discrete(&ci, u, Some(&mut node_pmf), Some(&mut u)); let child = sample_discrete(&ci, u, Some(&mut node_pmf), Some(&mut u));
pmf *= node_pmf; pmf *= node_pmf;
node_ind = if child == 0 { node_ind = if child == 0 { child0_idx } else { child1_idx };
node_ind + 1
} else { } else {
node.child_or_light_index() as usize if node_ind > 0 || node.light_bounds.importance(p, n, &self.all_light_bounds) > 0. {
}; let light_idx = node.child_or_light_index() as usize;
} else { return Some(SampledLight::new(self.light(light_idx), pmf));
if node_ind > 0
|| node.light_bounds.importance(p, n, &self.all_light_bounds) > 0.
{
return Some(SampledLight::new(
self.lights[node.child_or_light_index() as usize].clone(),
pmf,
));
} }
return None; return None;
} }
} }
} }
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float {
let light_ptr = light as *const Light;
let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
let n_infinite = self.infinite_lights_len as Float;
let inf_start = self.infinite_lights;
let inf_end = unsafe { self.infinite_lights.add(self.infinite_lights_len as usize) };
if light_ptr >= inf_start && light_ptr < inf_end {
return 1.0 / (n_infinite + empty_nodes);
} }
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Arc<Light>) -> Float { let finite_start = self.lights;
let ptr = Arc::as_ptr(light) as usize; let finite_end = unsafe { self.lights.add(self.lights_len as usize) };
let empty_nodes = if self.nodes.is_empty() { 0. } else { 1. };
if self.light_to_bit_trail.contains_key(&ptr) { if light_ptr < finite_start || light_ptr >= finite_end {
return 1. / (self.infinite_lights.len() as Float + empty_nodes); return 0.0;
} }
let mut bit_trail = self.light_to_bit_trail[&ptr]; let light_index = unsafe { light_ptr.offset_from(finite_start) as usize };
let mut bit_trail = self.bit_trail(light_index);
let p_inf = n_infinite / (n_infinite + empty_nodes);
let mut pmf = 1.0 - p_inf;
let mut node_ind = 0;
let p = ctx.p(); let p = ctx.p();
let n = ctx.ns; let n = ctx.ns;
let p_inf = self.infinite_lights.len() as Float
/ (self.infinite_lights.len() as Float + empty_nodes);
let mut pmf = 1. - p_inf;
let mut node_ind = 0;
loop { loop {
let node = self.nodes[node_ind]; let node = self.node(node_ind);
if node.is_leaf() { if node.is_leaf() {
return pmf; return pmf;
} }
let child0 = self.nodes[node_ind + 1]; let child0 = self.node(node_ind + 1);
let child1 = self.nodes[node.child_or_light_index() as usize]; let child1 = self.node(node.child_or_light_index() as usize);
let ci = [ let ci = [
child0.light_bounds.importance(p, n, &self.all_light_bounds), child0.light_bounds.importance(p, n, &self.all_light_bounds),
child1.light_bounds.importance(p, n, &self.all_light_bounds), child1.light_bounds.importance(p, n, &self.all_light_bounds),
]; ];
pmf *= ci[bit_trail & 1] / (ci[0] + ci[1]);
node_ind = if (bit_trail & 1) != 0 { let sum_importance = ci[0] + ci[1];
if sum_importance == 0.0 {
return 0.0;
}
let which_child = (bit_trail & 1) as usize;
// Update probability: prob of picking the correct child
pmf *= ci[which_child] / sum_importance;
// Advance
node_ind = if which_child == 1 {
node.child_or_light_index() as usize node.child_or_light_index() as usize
} else { } else {
node_ind + 1 node_ind + 1
}; };
bit_trail >>= 1; bit_trail >>= 1;
} }
} }
fn sample(&self, u: Float) -> Option<SampledLight> { fn sample(&self, u: Float) -> Option<SampledLight> {
if self.lights.is_empty() { if self.lights_len == 0 {
return None; return None;
} }
let light_ind = let light_ind = (u * self.lights_len as Float).min(self.lights_len as Float - 1.) as usize;
(u * self.lights.len() as Float).min(self.lights.len() as Float - 1.) as usize;
Some(SampledLight::new( Some(SampledLight::new(
self.lights[light_ind].clone(), self.light(light_ind),
1. / self.lights.len() as Float, 1. / self.lights_len as Float,
)) ))
} }
fn pmf(&self, _light: &Arc<Light>) -> Float { fn pmf(&self, _light: &Light) -> Float {
if self.lights.is_empty() { if self.lights_len == 0 {
return 0.; return 0.;
} }
1. / self.lights.len() as Float 1. / self.lights_len as Float
} }
} }

View file

@ -1,16 +1,24 @@
use crate::core::light::{LightBase, LightLiSample, LightSampleContext, LightTrait}; use crate::core::geometry::{
Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use crate::core::interaction::{Interaction, InteractionBase, InteractionTrait, SimpleInteraction};
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::{Float, PI};
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct SpotLight { pub struct SpotLight {
pub base: LightBase, pub base: LightBase,
pub iemit_coeffs: [Float; 32], pub iemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub cos_falloff_start: Float, pub cos_falloff_start: Float,
pub cos_falloff_end: Float, pub cos_falloff_end: Float,
} }
impl SpotLightData { impl SpotLight {
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
let cos_theta = w.z(); // assuming normalized in light space let cos_theta = w.z(); // assuming normalized in light space
let falloff = crate::utils::math::smooth_step( let falloff = crate::utils::math::smooth_step(
@ -18,8 +26,7 @@ impl SpotLightData {
self.cos_falloff_end, self.cos_falloff_end,
self.cos_falloff_start, self.cos_falloff_start,
); );
let spectrum = DenselySampledSpectrum::from_array(&self.iemit_coeffs); falloff * self.scale * self.iemit.sample(lambda)
falloff * self.scale * spectrum.sample(lambda)
} }
} }
@ -42,9 +49,10 @@ impl LightTrait for SpotLight {
let p: Point3f = pi.into(); let p: Point3f = pi.into();
let wi = (p - ctx.p()).normalize(); let wi = (p - ctx.p()).normalize();
let w_light = self.base.render_from_light.apply_inverse_vector(-wi); let w_light = self.base.render_from_light.apply_inverse_vector(-wi);
let li = self.i(w_light, *lambda) / p.distance_squared(ctx.p()); let li = self.i(w_light, lambda) / p.distance_squared(ctx.p());
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone())); let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface);
let intr = SimpleInteraction::new(base);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
} }
@ -78,7 +86,7 @@ impl LightTrait for SpotLight {
* self.iemit.sample(&lambda) * self.iemit.sample(&lambda)
* 2. * 2.
* PI * PI
* ((1. - self.cos_fallof_start) + (self.cos_fallof_start - self.cos_fallof_end) / 2.) * ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.)
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
@ -98,12 +106,12 @@ impl LightTrait for SpotLight {
.apply_to_vector(Vector3f::new(0., 0., 1.)) .apply_to_vector(Vector3f::new(0., 0., 1.))
.normalize(); .normalize();
let phi = self.scale * self.iemit.max_value() * 4. * PI; let phi = self.scale * self.iemit.max_value() * 4. * PI;
let cos_theta_e = (self.cos_fallof_end.acos() - self.cos_fallof_start.acos()).cos(); let cos_theta_e = (self.cos_falloff_end.acos() - self.cos_falloff_start.acos()).cos();
Some(LightBounds::new( Some(LightBounds::new(
&Bounds3f::from_points(p, p), &Bounds3f::from_points(p, p),
w, w,
phi, phi,
self.cos_fallof_start, self.cos_falloff_start,
cos_theta_e, cos_theta_e,
false, false,
)) ))

View file

@ -0,0 +1,340 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedDiffuseMaterial {
pub normal_map: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>,
pub albedo: Ptr<GPUSpectrumTexture>,
pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>,
pub thickness: Ptr<GPUFloatTexture>,
pub g: Ptr<GPUFloatTexture>,
pub eta: Ptr<Spectrum>,
pub remap_roughness: bool,
pub max_depth: usize,
pub n_samples: usize,
}
impl CoatedDiffuseMaterial {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
reflectance: &GPUSpectrumTexture,
u_roughness: &GPUFloatTexture,
v_roughness: &GPUFloatTexture,
thickness: &GPUFloatTexture,
albedo: &GPUSpectrumTexture,
g: &GPUFloatTexture,
eta: &Spectrum,
displacement: &GPUFloatTexture,
normal_map: &Image,
remap_roughness: bool,
max_depth: usize,
n_samples: usize,
) -> Self {
Self {
displacement: Ptr::from(displacement),
normal_map: Ptr::from(normal_map),
reflectance: Ptr::from(reflectance),
albedo: Ptr::from(albedo),
u_roughness: Ptr::from(u_roughness),
v_roughness: Ptr::from(v_roughness),
thickness: Ptr::from(thickness),
g: Ptr::from(g),
eta: Ptr::from(eta),
remap_roughness,
max_depth,
n_samples,
}
}
}
impl MaterialTrait for CoatedDiffuseMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
0.,
1.,
);
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx);
let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx);
if self.remap_roughness {
u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough);
v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough);
}
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if self.eta.is_constant() {
let mut lambda = *lambda;
lambda.terminate_secondary_inplace();
}
if sampled_eta == 0. {
sampled_eta = 1.
}
let a = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
0.,
1.,
);
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
let bxdf = BxDF::CoatedDiffuse(CoatedDiffuseBxDF::new(
DielectricBxDF::new(sampled_eta, distrib),
DiffuseBxDF::new(r),
thick,
a,
gg,
self.max_depth,
self.n_samples,
));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(
&[self.u_roughness, self.v_roughness, self.thickness, self.g],
&[self.reflectance, self.albedo],
)
}
fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial {
normal_map: Ptr<Image>,
displacement: Ptr<GPUFloatTexture>,
interface_uroughness: Ptr<GPUFloatTexture>,
interface_vroughness: Ptr<GPUFloatTexture>,
thickness: Ptr<GPUFloatTexture>,
interface_eta: Ptr<Spectrum>,
g: Ptr<GPUFloatTexture>,
albedo: Ptr<GPUSpectrumTexture>,
conductor_uroughness: Ptr<GPUFloatTexture>,
conductor_vroughness: Ptr<GPUFloatTexture>,
conductor_eta: Ptr<GPUSpectrumTexture>,
k: Ptr<GPUSpectrumTexture>,
reflectance: Ptr<GPUSpectrumTexture>,
remap_roughness: bool,
max_depth: u32,
n_samples: u32,
}
impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new(
normal_map: &Image,
displacement: &GPUFloatTexture,
interface_uroughness: &GPUFloatTexture,
interface_vroughness: &GPUFloatTexture,
thickness: &GPUFloatTexture,
interface_eta: &Spectrum,
g: &GPUFloatTexture,
albedo: &GPUSpectrumTexture,
conductor_uroughness: &GPUFloatTexture,
conductor_vroughness: &GPUFloatTexture,
conductor_eta: &GPUSpectrumTexture,
k: &GPUSpectrumTexture,
reflectance: &GPUSpectrumTexture,
remap_roughness: bool,
max_depth: u32,
n_samples: u32,
) -> Self {
Self {
displacement: Ptr::from(displacement),
normal_map: Ptr::from(normal_map),
interface_uroughness: Ptr::from(interface_uroughness),
interface_vroughness: Ptr::from(interface_vroughness),
thickness: Ptr::from(thickness),
interface_eta: Ptr::from(interface_eta),
g: Ptr::from(g),
albedo: Ptr::from(albedo),
conductor_uroughness: Ptr::from(conductor_uroughness),
conductor_vroughness: Ptr::from(conductor_vroughness),
conductor_eta: Ptr::from(conductor_eta),
k: Ptr::from(k),
reflectance: Ptr::from(reflectance),
remap_roughness,
max_depth,
n_samples,
}
}
}
impl MaterialTrait for CoatedConductorMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let mut iurough = tex_eval.evaluate_float(&self.interface_uroughness, ctx);
let mut ivrough = tex_eval.evaluate_float(&self.interface_vroughness, ctx);
if self.remap_roughness {
iurough = TrowbridgeReitzDistribution::roughness_to_alpha(iurough);
ivrough = TrowbridgeReitzDistribution::roughness_to_alpha(ivrough);
}
let interface_distrib = TrowbridgeReitzDistribution::new(iurough, ivrough);
let thick = tex_eval.evaluate_float(&self.thickness, ctx);
let mut ieta = self.interface_eta.evaluate(lambda[0]);
if self.interface_eta.is_constant() {
let mut lambda = *lambda;
lambda.terminate_secondary_inplace();
}
if ieta == 0. {
ieta = 1.;
}
let (mut ce, mut ck) = if !self.conductor_eta.is_null() {
let k_tex = self
.k
.as_ref()
.expect("CoatedConductor: 'k' must be provided if 'conductor_eta' is present");
let ce = tex_eval.evaluate_spectrum(&self.conductor_eta, ctx, lambda);
let ck = tex_eval.evaluate_spectrum(k_tex, ctx, lambda);
(ce, ck)
} else {
let r = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda),
0.,
0.9999,
);
let ce = SampledSpectrum::new(1.0);
let one_minus_r = SampledSpectrum::new(1.) - r;
let ck = 2. * r.sqrt() / SampledSpectrum::clamp_zero(&one_minus_r).sqrt();
(ce, ck)
};
ce /= ieta;
ck /= ieta;
let mut curough = tex_eval.evaluate_float(&self.conductor_uroughness, ctx);
let mut cvrough = tex_eval.evaluate_float(&self.conductor_vroughness, ctx);
if self.remap_roughness {
curough = TrowbridgeReitzDistribution::roughness_to_alpha(curough);
cvrough = TrowbridgeReitzDistribution::roughness_to_alpha(cvrough);
}
let conductor_distrib = TrowbridgeReitzDistribution::new(curough, cvrough);
let a = SampledSpectrum::clamp(
&tex_eval.evaluate_spectrum(&self.albedo, ctx, lambda),
0.,
1.,
);
let gg = clamp(tex_eval.evaluate_float(&self.g, ctx), -1., 1.);
let bxdf = BxDF::CoatedConductor(CoatedConductorBxDF::new(
DielectricBxDF::new(ieta, interface_distrib),
ConductorBxDF::new(&conductor_distrib, ce, ck),
thick,
a,
gg,
self.max_depth as usize,
self.n_samples as usize,
));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
let float_textures = [
self.interface_uroughness,
self.interface_vroughness,
self.thickness,
self.g,
self.conductor_uroughness,
self.conductor_vroughness,
];
let mut spectrum_textures = Vec::with_capacity(4);
spectrum_textures.push(self.albedo);
if !self.conductor_eta.is_null() {
spectrum_textures.push(self.conductor_eta);
}
if !self.k.is_null() {
spectrum_textures.push(self.k);
}
if !self.conductor_eta.is_null() {
spectrum_textures.push(self.reflectance);
}
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
}
fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,187 @@
use crate::Float;
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
MeasuredBxDF, MeasuredBxDFData,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::{BSSRDF, BSSRDFTable};
use crate::core::bxdf::BxDF;
use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::textures::GPUSpectrumMixTexture;
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HairMaterial {
pub sigma_a: Ptr<GPUSpectrumTexture>,
pub color: Ptr<GPUSpectrumTexture>,
pub eumelanin: Ptr<GPUFloatTexture>,
pub pheomelanin: Ptr<GPUFloatTexture>,
pub eta: Ptr<GPUFloatTexture>,
pub beta_m: Ptr<GPUFloatTexture>,
pub beta_n: Ptr<GPUFloatTexture>,
pub alpha: Ptr<GPUFloatTexture>,
}
impl HairMaterial {
#[cfg(not(target_os = "cuda"))]
pub fn new(
sigma_a: Ptr<GPUSpectrumTexture>,
color: Ptr<GPUSpectrumTexture>,
eumelanin: Ptr<GPUFloatTexture>,
pheomelanin: Ptr<GPUFloatTexture>,
eta: Ptr<GPUFloatTexture>,
beta_m: Ptr<GPUFloatTexture>,
beta_n: Ptr<GPUFloatTexture>,
alpha: Ptr<GPUFloatTexture>,
) -> Self {
Self {
sigma_a,
color,
eumelanin,
pheomelanin,
eta,
beta_m,
beta_n,
alpha,
}
}
}
impl MaterialTrait for HairMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
todo!()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
Ptr::null()
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MeasuredMaterial {
pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<Image>,
pub brdf: Ptr<MeasuredBxDFData>,
}
impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
// MeasuredBxDF::new(&self.brdf, lambda)
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
true
}
fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SubsurfaceMaterial {
pub normal_map: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>,
pub sigma_a: Ptr<GPUSpectrumTexture>,
pub sigma_s: Ptr<GPUSpectrumMixTexture>,
pub reflectance: Ptr<GPUSpectrumMixTexture>,
pub mfp: Ptr<GPUSpectrumMixTexture>,
pub eta: Float,
pub scale: Float,
pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>,
pub remap_roughness: bool,
pub table: BSSRDFTable,
}
impl MaterialTrait for SubsurfaceMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> Option<&Image> {
todo!()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
todo!()
}
fn has_subsurface_scattering(&self) -> bool {
true
}
}

View file

@ -0,0 +1,64 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ConductorMaterial {
pub displacement: Ptr<GPUFloatTexture>,
pub eta: Ptr<GPUSpectrumTexture>,
pub k: Ptr<GPUSpectrumTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>,
pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>,
pub remap_roughness: bool,
pub normal_map: Ptr<Image>,
}
impl MaterialTrait for ConductorMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(
&[self.u_roughness, self.v_roughness],
&[self.eta, self.k, self.reflectance],
)
}
fn get_normal_map(&self) -> Option<&Image> {
todo!()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
todo!()
}
fn has_subsurface_scattering(&self) -> bool {
todo!()
}
}

View file

@ -0,0 +1,123 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DielectricMaterial {
normal_map: Ptr<Image>,
displacement: Ptr<GPUFloatTexture>,
u_roughness: Ptr<GPUFloatTexture>,
v_roughness: Ptr<GPUFloatTexture>,
eta: Ptr<Spectrum>,
remap_roughness: bool,
}
impl MaterialTrait for DielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let mut sampled_eta = self.eta.evaluate(lambda[0]);
if !self.eta.is_constant() {
lambda.terminate_secondary();
}
if sampled_eta == 0.0 {
sampled_eta = 1.0;
}
let mut u_rough = tex_eval.evaluate_float(&self.u_roughness, ctx);
let mut v_rough = tex_eval.evaluate_float(&self.v_roughness, ctx);
if self.remap_roughness {
u_rough = TrowbridgeReitzDistribution::roughness_to_alpha(u_rough);
v_rough = TrowbridgeReitzDistribution::roughness_to_alpha(v_rough);
}
let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough);
let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[self.u_roughness, self.v_roughness], &[])
}
fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ThinDielectricMaterial {
pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<Image>,
pub eta: Ptr<Spectrum>,
}
impl MaterialTrait for ThinDielectricMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
true
}
fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,106 @@
use crate::Float;
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::clamp;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseMaterial {
pub normal_map: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>,
}
impl MaterialTrait for DiffuseMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda);
let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf))
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[], &[self.reflectance])
}
fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseTransmissionMaterial {
pub image: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUFloatTexture>,
pub transmittance: Ptr<GPUFloatTexture>,
pub scale: Float,
}
impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[self.reflectance, self.transmittance], &[])
}
fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.image)
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
self.displacement
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,86 @@
use crate::bxdfs::{
CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, HairBxDF,
};
use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF;
use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::hash::hash_float;
use crate::utils::math::clamp;
use crate::utils::{ArenaPtr, Ptr};
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MixMaterial {
pub amount: Ptr<GPUFloatTexture>,
pub materials: [ArenaPtr<Material>; 2],
}
impl MixMaterial {
pub fn choose_material<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
) -> Option<&Material> {
let amt = tex_eval.evaluate_float(&self.amount, ctx);
let index = if amt <= 0.0 {
0
} else if amt >= 1.0 {
1
} else {
let u = hash_float(&(ctx.p, ctx.wo));
if amt < u { 0 } else { 1 }
};
self.materials[index].get()
}
}
impl MaterialTrait for MixMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
if let Some(mat) = self.choose_material(tex_eval, ctx) {
mat.get_bsdf(tex_eval, ctx, lambda)
} else {
BSDF::default()
}
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[self.amount], &[])
}
fn get_normal_map(&self) -> Option<&Image> {
None
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
panic!(
"MixMaterial::get_displacement() shouldn't be called. \
Displacement is not supported on Mix materials directly."
);
}
fn has_subsurface_scattering(&self) -> bool {
false
}
}

View file

@ -0,0 +1,13 @@
pub mod coated;
pub mod complex;
pub mod conductor;
pub mod dielectric;
pub mod diffuse;
pub mod mix;
pub use coated::*;
pub use complex::*;
pub use conductor::*;
pub use dielectric::*;
pub use diffuse::*;
pub use mix::*;

View file

@ -1,58 +1,135 @@
use super::{ use crate::core::geometry::{
BilinearIntersection, BilinearPatchShape, Bounds3f, DirectionCone, Interaction, Normal3f, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Tuple, Vector3f,
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, VectorLike, spherical_quad_area,
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
}; };
use crate::core::geometry::{Tuple, VectorLike, spherical_quad_area}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::{Float, gamma}; use crate::core::pbrt::{Float, gamma};
use crate::core::shape::{Shape, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::Transform;
use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic}; use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic};
use crate::utils::mesh::BilinearPatchMesh; use crate::utils::mesh::BilinearPatchMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle, bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
}; };
use std::sync::Arc; use core::ops::Add;
use std::sync::OnceLock;
struct PatchData<'a> {
mesh: &'a BilinearPatchMesh,
p00: Point3f,
p10: Point3f,
p01: Point3f,
p11: Point3f,
n: Option<[Normal3f; 4]>,
uv: Option<[Point2f; 4]>,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct IntersectionData { struct IntersectionData {
t: Float, pub t: Float,
u: Float, pub u: Float,
v: Float, pub v: Float,
} }
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
struct TextureDerivative { struct TextureDerivative {
duds: Float, pub duds: Float,
dvds: Float, pub dvds: Float,
dudt: Float, pub dudt: Float,
dvdt: Float, pub dvdt: Float,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BilinearIntersection {
pub uv: Point2f,
pub t: Float,
}
impl BilinearIntersection {
pub fn new(uv: Point2f, t: Float) -> Self {
Self { uv, t }
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BilinearPatchShape {
pub mesh: BilinearPatchMesh,
pub blp_index: u32,
pub area: Float,
pub rectangle: bool,
} }
static BILINEAR_MESHES: OnceLock<Vec<Arc<BilinearPatchMesh>>> = OnceLock::new();
impl BilinearPatchShape { impl BilinearPatchShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4;
fn mesh(&self) -> BilinearPatchMesh {
self.mesh
}
pub fn new(_mesh: BilinearPatchMesh, mesh_index: usize, blp_index: usize) -> Self { #[inline(always)]
fn get_vertex_indices(&self) -> [usize; 4] {
unsafe {
let base_ptr = self
.mesh
.vertex_indices
.0
.add((self.blp_index as usize) * 4);
[
*base_ptr.add(0) as usize,
*base_ptr.add(1) as usize,
*base_ptr.add(2) as usize,
*base_ptr.add(3) as usize,
]
}
}
#[inline(always)]
fn get_points(&self) -> [Point3f; 4] {
let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe {
[
*self.mesh.p.0.add(v0),
*self.mesh.p.0.add(v1),
*self.mesh.p.0.add(v2),
*self.mesh.p.0.add(v3),
]
}
}
#[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 4]> {
if self.mesh.uv.is_null() {
return None;
}
let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.uv.0.add(v0),
*self.mesh.uv.0.add(v1),
*self.mesh.uv.0.add(v2),
*self.mesh.uv.0.add(v3),
])
}
}
#[inline(always)]
fn get_shading_normals(&self) -> Option<[Normal3f; 4]> {
if self.mesh.n.is_null() {
return None;
}
let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.n.0.add(v0),
*self.mesh.n.0.add(v1),
*self.mesh.n.0.add(v2),
*self.mesh.n.0.add(v3),
])
}
}
#[cfg(not(target_os = "cuda"))]
pub fn new(mesh: BilinearPatchMesh, blp_index: u32) -> Self {
let mut bp = BilinearPatchShape { let mut bp = BilinearPatchShape {
mesh_index, mesh,
blp_index, blp_index,
area: 0., area: 0.,
rectangle: false, rectangle: false,
}; };
let (p00, p10, p01, p11) = { let [p00, p10, p01, p11] = bp.get_points();
let data = bp.get_data();
(data.p00, data.p10, data.p01, data.p11)
};
bp.rectangle = bp.is_rectangle(p00, p10, p01, p11); bp.rectangle = bp.is_rectangle(p00, p10, p01, p11);
@ -84,40 +161,6 @@ impl BilinearPatchShape {
bp bp
} }
fn mesh(&self) -> &Arc<BilinearPatchMesh> {
let meshes = BILINEAR_MESHES
.get()
.expect("Mesh has not been initialized");
&meshes[self.mesh_index]
}
fn get_data(&self) -> PatchData<'_> {
let mesh = self.mesh();
let start_index = 4 * self.blp_index;
let v = &mesh.vertex_indices[start_index..start_index + 4];
let p00: Point3f = mesh.p[v[0]];
let p10: Point3f = mesh.p[v[1]];
let p01: Point3f = mesh.p[v[2]];
let p11: Point3f = mesh.p[v[3]];
let n = mesh
.n
.as_ref()
.map(|normals| [normals[v[0]], normals[v[1]], normals[v[2]], normals[v[3]]]);
let uv = mesh
.uv
.as_ref()
.map(|uvs| [uvs[v[0]], uvs[v[1]], uvs[v[2]], uvs[v[3]]]);
PatchData {
mesh,
p00,
p10,
p01,
p11,
n,
uv,
}
}
fn is_rectangle(&self, p00: Point3f, p10: Point3f, p01: Point3f, p11: Point3f) -> bool { fn is_rectangle(&self, p00: Point3f, p10: Point3f, p01: Point3f, p11: Point3f) -> bool {
if p00 == p01 || p01 == p11 || p11 == p10 || p10 == p00 { if p00 == p01 || p01 == p11 || p11 == p10 || p10 == p00 {
return false; return false;
@ -149,25 +192,26 @@ impl BilinearPatchShape {
&self, &self,
ray: &Ray, ray: &Ray,
t_max: Float, t_max: Float,
data: &PatchData, corners: &[Point3f; 4],
) -> Option<BilinearIntersection> { ) -> Option<BilinearIntersection> {
let a = (data.p10 - data.p00).cross(data.p01 - data.p11).dot(ray.d); let &[p00, p01, p10, p11] = corners;
let c = (data.p00 - ray.o).cross(ray.d).dot(data.p01 - data.p00); let a = (p10 - p00).cross(p01 - p11).dot(ray.d);
let b = (data.p10 - ray.o).cross(ray.d).dot(data.p11 - data.p10) - (a + c); let c = (p00 - ray.o).cross(ray.d).dot(p01 - p00);
let b = (p10 - ray.o).cross(ray.d).dot(p11 - p10) - (a + c);
let (u1, u2) = quadratic(a, b, c)?; let (u1, u2) = quadratic(a, b, c)?;
let eps = gamma(10) let eps = gamma(10)
* (ray.o.abs().max_component_value() * (ray.o.abs().max_component_value()
+ ray.d.abs().max_component_value() + ray.d.abs().max_component_value()
+ data.p00.abs().max_component_value() + p00.abs().max_component_value()
+ data.p10.abs().max_component_value() + p10.abs().max_component_value()
+ data.p01.abs().max_component_value() + p01.abs().max_component_value()
+ data.p11.abs().max_component_value()); + p11.abs().max_component_value());
let hit1 = self.check_candidate(u1, ray, data); let hit1 = self.check_candidate(u1, ray, corners);
let hit2 = if u1 != u2 { let hit2 = if u1 != u2 {
self.check_candidate(u2, ray, data) self.check_candidate(u2, ray, corners)
} else { } else {
None None
}; };
@ -183,12 +227,18 @@ impl BilinearPatchShape {
}) })
} }
fn check_candidate(&self, u: Float, ray: &Ray, data: &PatchData) -> Option<IntersectionData> { fn check_candidate(
&self,
u: Float,
ray: &Ray,
corners: &[Point3f; 4],
) -> Option<IntersectionData> {
if !(0.0..=1.0).contains(&u) { if !(0.0..=1.0).contains(&u) {
return None; return None;
} }
let uo: Point3f = lerp(u, data.p00, data.p10); let &[p00, p01, p10, p11] = corners;
let ud: Point3f = Point3f::from(lerp(u, data.p01, data.p11) - uo); let uo: Point3f = lerp(u, p00, p10);
let ud: Point3f = Point3f::from(lerp(u, p01, p11) - uo);
let deltao = uo - ray.o; let deltao = uo - ray.o;
let perp = ray.d.cross(ud.into()); let perp = ray.d.cross(ud.into());
let p2 = perp.norm_squared(); let p2 = perp.norm_squared();
@ -222,26 +272,25 @@ impl BilinearPatchShape {
fn interaction_from_intersection( fn interaction_from_intersection(
&self, &self,
data: &PatchData,
uv: Point2f, uv: Point2f,
time: Float, time: Float,
wo: Vector3f, wo: Vector3f,
) -> SurfaceInteraction { ) -> SurfaceInteraction {
// Base geom and derivatives // Base geom and derivatives
let p = lerp( let corners = self.get_points();
uv[0], let [p00, p01, p10, p11] = corners;
lerp(uv[1], data.p00, data.p01), let p = lerp(uv[0], lerp(uv[1], p00, p01), lerp(uv[1], p10, p11));
lerp(uv[1], data.p10, data.p11), let mut dpdu = lerp(uv[1], p10, p11) - lerp(uv[1], p00, p01);
); let mut dpdv = lerp(uv[0], p01, p11) - lerp(uv[0], p00, p10);
let mut dpdu = lerp(uv[1], data.p10, data.p11) - lerp(uv[1], data.p00, data.p01);
let mut dpdv = lerp(uv[0], data.p01, data.p11) - lerp(uv[0], data.p00, data.p10);
// If textured, apply coordinates // If textured, apply coordinates
let (st, derivatives) = self.apply_texture_coordinates(data, uv, &mut dpdu, &mut dpdv); let patch_uvs = self.get_uvs();
let (st, derivatives) =
self.apply_texture_coordinates(uv, patch_uvs.try_into().unwrap(), &mut dpdu, &mut dpdv);
// Compute second moments // Compute second moments
let n = Normal3f::from(dpdu.cross(dpdv).normalize()); let n = Normal3f::from(dpdu.cross(dpdv).normalize());
let (mut dndu, mut dndv) = self.calculate_surface_curvature(data, &dpdu, &dpdv, n); let (mut dndu, mut dndv) = self.calculate_surface_curvature(&corners, &dpdu, &dpdv, n);
if let Some(ref deriv) = derivatives { if let Some(ref deriv) = derivatives {
let dnds = Normal3f::from(dndu * deriv.duds + dndv * deriv.dvds); let dnds = Normal3f::from(dndu * deriv.duds + dndv * deriv.dvds);
let dndt = Normal3f::from(dndu * deriv.dudt + dndv * deriv.dvdt); let dndt = Normal3f::from(dndu * deriv.dudt + dndv * deriv.dvdt);
@ -249,31 +298,31 @@ impl BilinearPatchShape {
dndv = dndt; dndv = dndt;
} }
let p_abs_sum = data.p00.abs() let p_abs_sum = p00.abs()
+ Vector3f::from(data.p01.abs()) + Vector3f::from(p01.abs())
+ Vector3f::from(data.p10.abs()) + Vector3f::from(p10.abs())
+ Vector3f::from(data.p11.abs()); + Vector3f::from(p11.abs());
let p_error = gamma(6) * Vector3f::from(p_abs_sum); let p_error = gamma(6) * Vector3f::from(p_abs_sum);
let flip_normal = data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness; let flip_normal = self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness;
let pi = Point3fi::new_with_error(p, p_error); let pi = Point3fi::new_with_error(p, p_error);
let mut isect = let mut isect =
SurfaceInteraction::new(pi, st, wo, dpdu, dpdv, dndu, dndv, time, flip_normal); SurfaceInteraction::new(pi, st, wo, dpdu, dpdv, dndu, dndv, time, flip_normal);
if data.n.is_some() {
self.apply_shading_normals(&mut isect, data, uv, derivatives); self.apply_shading_normals(&mut isect, self.get_shading_normals(), uv, derivatives);
}
isect isect
} }
#[inline(always)]
fn apply_texture_coordinates( fn apply_texture_coordinates(
&self, &self,
data: &PatchData,
uv: Point2f, uv: Point2f,
patch_uvs: Option<[Point2f; 4]>,
dpdu: &mut Vector3f, dpdu: &mut Vector3f,
dpdv: &mut Vector3f, dpdv: &mut Vector3f,
) -> (Point2f, Option<TextureDerivative>) { ) -> (Point2f, Option<TextureDerivative>) {
let Some(uvs) = data.uv else { let Some(uvs) = patch_uvs else {
return (uv, None); return (uv, Some(TextureDerivative::default()));
}; };
let uv00 = uvs[0]; let uv00 = uvs[0];
let uv01 = uvs[1]; let uv01 = uvs[1];
@ -297,6 +346,7 @@ impl BilinearPatchShape {
if dpdu.cross(*dpdv).dot(dpds.cross(dpdt)) < 0. { if dpdu.cross(*dpdv).dot(dpds.cross(dpdt)) < 0. {
dpdt = -dpdt; dpdt = -dpdt;
} }
*dpdu = dpds; *dpdu = dpds;
*dpdv = dpdt; *dpdv = dpdt;
@ -309,32 +359,33 @@ impl BilinearPatchShape {
(st, Some(factors)) (st, Some(factors))
} }
#[inline(always)]
fn calculate_base_derivatives( fn calculate_base_derivatives(
&self, &self,
data: &PatchData, corners: &[Point3f; 4],
uv: Point2f, uv: Point2f,
) -> (Point3f, Vector3f, Vector3f) { ) -> (Point3f, Vector3f, Vector3f) {
let p = lerp( let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
uv[0],
lerp(uv[1], data.p00, data.p01), let p = lerp(uv[0], lerp(uv[1], p00, p01), lerp(uv[1], p10, p11));
lerp(uv[1], data.p10, data.p11), let dpdu = lerp(uv[1], p10, p11) - lerp(uv[1], p00, p01);
); let dpdv = lerp(uv[0], p01, p11) - lerp(uv[0], p00, p10);
let dpdu = lerp(uv[1], data.p10, data.p11) - lerp(uv[1], data.p00, data.p01);
let dpdv = lerp(uv[0], data.p01, data.p11) - lerp(uv[0], data.p00, data.p10);
(p, dpdu, dpdv) (p, dpdu, dpdv)
} }
#[inline(always)]
fn calculate_surface_curvature( fn calculate_surface_curvature(
&self, &self,
data: &PatchData, corners: &[Point3f; 4],
dpdu: &Vector3f, dpdu: &Vector3f,
dpdv: &Vector3f, dpdv: &Vector3f,
n: Normal3f, n: Normal3f,
) -> (Normal3f, Normal3f) { ) -> (Normal3f, Normal3f) {
let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
let e = dpdu.dot(*dpdu); let e = dpdu.dot(*dpdu);
let f = dpdu.dot(*dpdv); let f = dpdu.dot(*dpdv);
let g = dpdv.dot(*dpdv); let g = dpdv.dot(*dpdv);
let d2pduv = (data.p00 - data.p01) + (data.p11 - data.p10); let d2pduv = (p00 - p01) + (p11 - p10);
let d2pduu = Vector3f::zero(); let d2pduu = Vector3f::zero();
let d2pdvv = Vector3f::zero(); let d2pdvv = Vector3f::zero();
@ -357,11 +408,13 @@ impl BilinearPatchShape {
fn apply_shading_normals( fn apply_shading_normals(
&self, &self,
isect: &mut SurfaceInteraction, isect: &mut SurfaceInteraction,
data: &PatchData, shading_normals: Option<[Normal3f; 4]>,
uv: Point2f, uv: Point2f,
derivatives: Option<TextureDerivative>, derivatives: Option<TextureDerivative>,
) { ) {
let Some(normals) = data.n else { return }; let Some(normals) = shading_normals else {
return;
};
let n00 = normals[1]; let n00 = normals[1];
let n10 = normals[1]; let n10 = normals[1];
let n01 = normals[2]; let n01 = normals[2];
@ -390,10 +443,7 @@ impl BilinearPatchShape {
fn sample_area_and_pdf(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_area_and_pdf(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
ss.intr.get_common_mut().time = ctx.time;
let mut intr_clone = (*ss.intr).clone();
intr_clone.common.time = ctx.time;
ss.intr = Arc::new(intr_clone);
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
let dist_sq = wi.norm_squared(); let dist_sq = wi.norm_squared();
@ -412,16 +462,18 @@ impl BilinearPatchShape {
if ss.pdf.is_infinite() { None } else { Some(ss) } if ss.pdf.is_infinite() { None } else { Some(ss) }
} }
fn sample_parametric_coords(&self, data: &PatchData, u: Point2f) -> (Point2f, Float) { fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) {
if let Some(image_distrib) = &data.mesh.image_distribution { let (p00, p10, p01, p11) = (corners[0], corners[1], corners[2], corners[3]);
if !self.mesh.image_distribution.is_null() {
let image_distrib = self.mesh.image_distribution;
let (uv, pdf, _) = image_distrib.sample(u); let (uv, pdf, _) = image_distrib.sample(u);
(uv, pdf) (uv, pdf)
} else if !self.rectangle { } else if !self.rectangle {
let w = [ let w = [
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(), (p10 - p00).cross(p01 - p00).norm(),
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(), (p10 - p00).cross(p11 - p10).norm(),
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(), (p01 - p00).cross(p11 - p01).norm(),
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(), (p11 - p10).cross(p11 - p01).norm(),
]; ];
let uv = sample_bilinear(u, &w); let uv = sample_bilinear(u, &w);
let pdf = bilinear_pdf(uv, &w); let pdf = bilinear_pdf(uv, &w);
@ -433,11 +485,12 @@ impl BilinearPatchShape {
fn sample_solid_angle( fn sample_solid_angle(
&self, &self,
data: &PatchData, corners: &[Point3f; 4],
ctx: &ShapeSampleContext, ctx: &ShapeSampleContext,
u: Point2f, u: Point2f,
corner_dirs: &[Vector3f; 4], corner_dirs: &[Vector3f; 4],
) -> Option<ShapeSample> { ) -> Option<ShapeSample> {
let (p00, p10, p01, _p11) = (corners[0], corners[1], corners[2], corners[3]);
let mut pdf = 1.; let mut pdf = 1.;
if ctx.ns != Normal3f::zero() { if ctx.ns != Normal3f::zero() {
let w = [ let w = [
@ -450,19 +503,21 @@ impl BilinearPatchShape {
pdf *= bilinear_pdf(u, &w); pdf *= bilinear_pdf(u, &w);
} }
let eu = data.p10 - data.p00; let eu = p10 - p00;
let ev = data.p01 - data.p00; let ev = p01 - p00;
let (p, quad_pdf) = sample_spherical_rectangle(ctx.p(), data.p00, eu, ev, u); let (p, quad_pdf) = sample_spherical_rectangle(ctx.p(), p00, eu, ev, u);
pdf *= quad_pdf?; pdf *= quad_pdf?;
// Compute (u, v) and surface normal for sampled points on rectangle // Compute (u, v) and surface normal for sampled points on rectangle
let uv = Point2f::new( let uv = Point2f::new(
(p - data.p00).dot(eu) / data.p10.distance_squared(data.p00), (p - p00).dot(eu) / p10.distance_squared(p00),
(p - data.p00).dot(ev) / data.p01.distance_squared(data.p00), (p - p00).dot(ev) / p01.distance_squared(p00),
); );
let n = self.compute_sampled_normal(data, &eu, &ev, uv); let patch_uvs = self.get_uvs();
let st = data.uv.map_or(uv, |uvs| { let patch_normals = self.get_shading_normals();
let n = self.compute_sampled_normal(patch_normals, &eu, &ev, uv);
let st = patch_uvs.map_or(uv, |uvs| {
lerp( lerp(
uv[0], uv[0],
lerp(uv[1], uvs[0], uvs[1]), lerp(uv[1], uvs[0], uvs[1]),
@ -474,29 +529,29 @@ impl BilinearPatchShape {
let mut intr = SurfaceInteraction::new_simple(pi, n, st); let mut intr = SurfaceInteraction::new_simple(pi, n, st);
intr.common.time = ctx.time; intr.common.time = ctx.time;
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(intr), intr: Interaction::Surface(intr),
pdf, pdf,
}) })
} }
fn compute_sampled_normal( fn compute_sampled_normal(
&self, &self,
data: &PatchData, patch_normals: Option<[Normal3f; 4]>,
dpdu: &Vector3f, dpdu: &Vector3f,
dpdv: &Vector3f, dpdv: &Vector3f,
uv: Point2f, uv: Point2f,
) -> Normal3f { ) -> Normal3f {
let mut n = Normal3f::from(dpdu.cross(*dpdv).normalize()); let mut n = Normal3f::from(dpdu.cross(*dpdv).normalize());
if let Some(normals) = data.n { if let Some(normals) = patch_normals {
// Apply interpolated shading normal to orient the geometric normal // Apply interpolated shading normal to orient the geometric normal
let ns = lerp( let ns = lerp(
uv[0], uv[0],
lerp(uv[1], normals[0], normals[2]), lerp(uv[1], normals[0], normals[2]),
lerp(uv[1], normals[1], normals[3]), lerp(uv[1], normals[1], normals[3]),
); );
n = n.face_forward(ns.into()); n = n.face_forward(ns);
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness { } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n = -n; n = -n;
} }
n n
@ -511,36 +566,33 @@ impl ShapeTrait for BilinearPatchShape {
#[inline] #[inline]
fn normal_bounds(&self) -> DirectionCone { fn normal_bounds(&self) -> DirectionCone {
let data = self.get_data(); let [p00, p01, p10, p11] = self.get_points();
if data.p00 == data.p10 let normals = self.get_shading_normals();
|| data.p10 == data.p11 if p00 == p10 || p10 == p11 || p11 == p01 || p01 == p00 {
|| data.p11 == data.p01 let dpdu = lerp(0.5, p10, p11) - lerp(0.5, p00, p01);
|| data.p01 == data.p00 let dpdv = lerp(0.5, p01, p11) - lerp(0.5, p00, p10);
{
let dpdu = lerp(0.5, data.p10, data.p11) - lerp(0.5, data.p00, data.p01);
let dpdv = lerp(0.5, data.p01, data.p11) - lerp(0.5, data.p00, data.p10);
let mut n = Normal3f::from(dpdu.cross(dpdv).normalize()); let mut n = Normal3f::from(dpdu.cross(dpdv).normalize());
if let Some(normals) = data.n { if let Some(normals) = normals {
let interp_n = (normals[0] + normals[1] + normals[2] + normals[3]) / 4.; let interp_n = (normals[0] + normals[1] + normals[2] + normals[3]) / 4.;
n = n.face_forward(interp_n.into()); n = n.face_forward(interp_n);
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness { } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n *= -1.; n *= -1.;
} }
return DirectionCone::new_from_vector(Vector3f::from(n)); return DirectionCone::new_from_vector(Vector3f::from(n));
} }
// Compute bilinear patch normals n10, n01, and n11 // Compute bilinear patch normals n10, n01, and n11
let mut n00 = Normal3f::from((data.p10 - data.p00).cross(data.p01 - data.p00).normalize()); let mut n00 = Normal3f::from((p10 - p00).cross(p01 - p00).normalize());
let mut n10 = Normal3f::from((data.p11 - data.p10).cross(data.p00 - data.p10).normalize()); let mut n10 = Normal3f::from((p11 - p10).cross(p00 - p10).normalize());
let mut n01 = Normal3f::from((data.p00 - data.p01).cross(data.p11 - data.p01).normalize()); let mut n01 = Normal3f::from((p00 - p01).cross(p11 - p01).normalize());
let mut n11 = Normal3f::from((data.p01 - data.p11).cross(data.p10 - data.p11).normalize()); let mut n11 = Normal3f::from((p01 - p11).cross(p10 - p11).normalize());
if let Some(normals) = data.n { if let Some(normals) = normals {
n00 = n00.face_forward(normals[0].into()); n00 = n00.face_forward(normals[0]);
n10 = n10.face_forward(normals[1].into()); n10 = n10.face_forward(normals[1]);
n01 = n01.face_forward(normals[2].into()); n01 = n01.face_forward(normals[2]);
n11 = n11.face_forward(normals[3].into()); n11 = n11.face_forward(normals[3]);
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness { } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n00 = -n00; n00 = -n00;
n10 = -n10; n10 = -n10;
n01 = -n01; n01 = -n01;
@ -559,16 +611,17 @@ impl ShapeTrait for BilinearPatchShape {
#[inline] #[inline]
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
let data = self.get_data(); let [p00, p01, p10, p11] = self.get_points();
Bounds3f::from_points(data.p00, data.p01).union(Bounds3f::from_points(data.p10, data.p11)) Bounds3f::from_points(p00, p01).union(Bounds3f::from_points(p10, p11))
} }
#[inline] #[inline]
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let t_max_val = t_max?; let t_max_val = t_max?;
let data = self.get_data(); if let Some(bilinear_hit) =
if let Some(bilinear_hit) = self.intersect_bilinear_patch(ray, t_max_val, &data) { self.intersect_bilinear_patch(ray, t_max_val, &self.get_points())
let intr = self.interaction_from_intersection(&data, bilinear_hit.uv, ray.time, -ray.d); {
let intr = self.interaction_from_intersection(bilinear_hit.uv, ray.time, -ray.d);
Some(ShapeIntersection { Some(ShapeIntersection {
intr, intr,
@ -582,25 +635,28 @@ impl ShapeTrait for BilinearPatchShape {
#[inline] #[inline]
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
let t_max_val = t_max.unwrap_or(Float::INFINITY); let t_max_val = t_max.unwrap_or(Float::INFINITY);
let data = self.get_data(); let corners = self.get_points();
self.intersect_bilinear_patch(ray, t_max_val, &data) self.intersect_bilinear_patch(ray, t_max_val, &corners)
.is_some() .is_some()
} }
#[inline] #[inline]
fn sample(&self, u: Point2f) -> Option<ShapeSample> { fn sample(&self, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data(); let corners = self.get_points();
let [p00, p01, p10, p11] = corners;
// Sample bilinear patch parametric coordinate (u, v) // Sample bilinear patch parametric coordinate (u, v)
let (uv, pdf) = self.sample_parametric_coords(&data, u); let (uv, pdf) = self.sample_parametric_coords(&corners, u);
// Compute bilinear patch geometric quantities at sampled (u, v) // Compute bilinear patch geometric quantities at sampled (u, v)
let (p, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv); let (p, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
if dpdu.norm_squared() == 0. || dpdv.norm_squared() == 0. { if dpdu.norm_squared() == 0. || dpdv.norm_squared() == 0. {
return None; return None;
} }
// Compute surface normal for sampled bilinear patch (u, v) // Compute surface normal for sampled bilinear patch (u, v)
let n = self.compute_sampled_normal(&data, &dpdu, &dpdv, uv); let patch_normals = self.get_shading_normals();
let st = data.uv.map_or(uv, |patch_uvs| { let patch_uvs = self.get_uvs();
let n = self.compute_sampled_normal(patch_normals, &dpdu, &dpdv, uv);
let st = patch_uvs.map_or(uv, |patch_uvs| {
lerp( lerp(
uv[0], uv[0],
lerp(uv[1], patch_uvs[0], patch_uvs[1]), lerp(uv[1], patch_uvs[0], patch_uvs[1]),
@ -608,33 +664,34 @@ impl ShapeTrait for BilinearPatchShape {
) )
}); });
let p_abs_sum = data.p00.abs() let p_abs_sum = p00.abs()
+ Vector3f::from(data.p01.abs()) + Vector3f::from(p01.abs())
+ Vector3f::from(data.p10.abs()) + Vector3f::from(p10.abs())
+ Vector3f::from(data.p11.abs()); + Vector3f::from(p11.abs());
let p_error = gamma(6) * Vector3f::from(p_abs_sum); let p_error = gamma(6) * Vector3f::from(p_abs_sum);
let pi = Point3fi::new_with_error(p, p_error); let pi = Point3fi::new_with_error(p, p_error);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, st)), intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, st)),
pdf: pdf / dpdu.cross(dpdv).norm(), pdf: pdf / dpdu.cross(dpdv).norm(),
}) })
} }
#[inline] #[inline]
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data(); let corners = self.get_points();
let v00 = (data.p00 - ctx.p()).normalize(); let [p00, p01, p10, p11] = corners;
let v10 = (data.p10 - ctx.p()).normalize(); let v00 = (p00 - ctx.p()).normalize();
let v01 = (data.p01 - ctx.p()).normalize(); let v10 = (p10 - ctx.p()).normalize();
let v11 = (data.p11 - ctx.p()).normalize(); let v01 = (p01 - ctx.p()).normalize();
let v11 = (p11 - ctx.p()).normalize();
let use_area_sampling = self.rectangle let use_area_sampling = self.rectangle
|| data.mesh.image_distribution.is_some() || !self.mesh.image_distribution.is_null()
|| spherical_quad_area(v00, v10, v11, v01) <= Self::MIN_SPHERICAL_SAMPLE_AREA; || spherical_quad_area(v00, v10, v11, v01) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
if use_area_sampling { if use_area_sampling {
self.sample_area_and_pdf(ctx, u) self.sample_area_and_pdf(ctx, u)
} else { } else {
self.sample_solid_angle(&data, ctx, u, &[v00, v10, v01, v11]) self.sample_solid_angle(&corners, ctx, u, &[v00, v10, v01, v11])
} }
} }
@ -644,28 +701,30 @@ impl ShapeTrait for BilinearPatchShape {
return 0.0; return 0.0;
}; };
let data = self.get_data(); let corners = self.get_points();
let uv = if let Some(uvs) = &data.mesh.uv { let [p00, p01, p10, p11] = corners;
Point2f::invert_bilinear(si.uv, uvs) let patch_uvs = self.get_uvs();
let uv = if let Some(uvs) = patch_uvs {
Point2f::invert_bilinear(si.common.uv, &uvs)
} else { } else {
si.uv si.common.uv
}; };
let param_pdf = if let Some(image_distrib) = &data.mesh.image_distribution { let param_pdf = if !self.mesh.image_distribution.is_null() {
image_distrib.pdf(uv) self.mesh.image_distribution.pdf(uv)
} else if self.rectangle { } else if self.rectangle {
let w = [ let w = [
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(), (p10 - p00).cross(p01 - p00).norm(),
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(), (p10 - p00).cross(p11 - p10).norm(),
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(), (p01 - p00).cross(p11 - p01).norm(),
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(), (p11 - p10).cross(p11 - p01).norm(),
]; ];
bilinear_pdf(uv, &w) bilinear_pdf(uv, &w)
} else { } else {
1. 1.
}; };
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv); let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
let cross = dpdu.cross(dpdv).norm(); let cross = dpdu.cross(dpdv).norm();
if cross == 0. { 0. } else { param_pdf / cross } if cross == 0. { 0. } else { param_pdf / cross }
} }
@ -677,15 +736,16 @@ impl ShapeTrait for BilinearPatchShape {
return 0.; return 0.;
}; };
let data = self.get_data(); let corners = self.get_points();
let [p00, p01, p10, p11] = corners;
let v00 = (data.p00 - ctx.p()).normalize(); let v00 = (p00 - ctx.p()).normalize();
let v10 = (data.p10 - ctx.p()).normalize(); let v10 = (p10 - ctx.p()).normalize();
let v01 = (data.p01 - ctx.p()).normalize(); let v01 = (p01 - ctx.p()).normalize();
let v11 = (data.p11 - ctx.p()).normalize(); let v11 = (p11 - ctx.p()).normalize();
let use_area_sampling = !self.rectangle let use_area_sampling = !self.rectangle
|| data.mesh.image_distribution.is_some() || !self.mesh.image_distribution.is_null()
|| spherical_quad_area(v00, v10, v01, v11) <= Self::MIN_SPHERICAL_SAMPLE_AREA; || spherical_quad_area(v00, v10, v01, v11) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
if use_area_sampling { if use_area_sampling {
@ -709,9 +769,9 @@ impl ShapeTrait for BilinearPatchShape {
]; ];
let u = invert_spherical_rectangle_sample( let u = invert_spherical_rectangle_sample(
ctx.p(), ctx.p(),
data.p00, p00,
data.p10 - data.p00, p10 - p00,
data.p01 - data.p00, p01 - p00,
isect.intr.p(), isect.intr.p(),
); );
pdf *= bilinear_pdf(u, &w); pdf *= bilinear_pdf(u, &w);

View file

@ -1,21 +1,95 @@
use crate::core::interaction::InteractionTrait; use crate::Float;
use crate::core::geometry::{
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
VectorLike,
};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::math::{clamp, lerp, square}; use crate::utils::math::{clamp, lerp, square};
use crate::utils::splines::{ use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier, bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
}; };
use crate::utils::transform::look_at; use crate::utils::transform::{Transform, look_at};
use super::{ #[repr(C)]
Bounds3f, CurveCommon, CurveShape, CurveType, DirectionCone, Float, Interaction, Normal3f, #[derive(Debug, Clone, Copy, PartialEq)]
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, pub enum CurveType {
ShapeTrait, SurfaceInteraction, Transform, Vector2f, Vector3f, VectorLike, Flat,
}; Cylinder,
use std::sync::Arc; Ribbon,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CurveCommon {
pub curve_type: CurveType,
pub cp_obj: [Point3f; 4],
pub width: [Float; 2],
pub n: [Normal3f; 2],
pub normal_angle: Float,
pub inv_sin_normal_angle: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl CurveCommon {
#[allow(clippy::too_many_arguments)]
pub fn new(
c: &[Point3f],
w0: Float,
w1: Float,
curve_type: CurveType,
norm: &[Normal3f],
render_from_object: Transform,
object_from_render: Transform,
reverse_orientation: bool,
) -> Self {
let transform_swap_handedness = render_from_object.swaps_handedness();
let width = [w0, w1];
assert_eq!(c.len(), 4);
let cp_obj: [Point3f; 4] = c[..4].try_into().unwrap();
let mut n = [Normal3f::default(); 2];
let mut normal_angle: Float = 0.;
let mut inv_sin_normal_angle: Float = 0.;
if norm.len() == 2 {
n[0] = norm[0].normalize();
n[1] = norm[1].normalize();
normal_angle = n[0].angle_between(n[1]);
inv_sin_normal_angle = 1. / normal_angle.sin();
}
Self {
curve_type,
cp_obj,
width,
n,
normal_angle,
inv_sin_normal_angle,
render_from_object,
object_from_render,
reverse_orientation,
transform_swap_handedness,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CurveShape {
pub common: CurveCommon,
pub u_min: Float,
pub u_max: Float,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct IntersectionContext { struct IntersectionContext {
ray: Ray, pub ray: Ray,
object_from_ray: Arc<Transform>, pub object_from_ray: Transform,
common: CurveCommon, pub common: CurveCommon,
} }
impl CurveShape { impl CurveShape {
@ -32,7 +106,6 @@ impl CurveShape {
.common .common
.object_from_render .object_from_render
.apply_to_ray(r, &mut Some(t_max)); .apply_to_ray(r, &mut Some(t_max));
// Get object-space control points for curve segment, cpObj
let cp_obj = cubic_bezier_control_points(&self.common.cp_obj, self.u_min, self.u_max); let cp_obj = cubic_bezier_control_points(&self.common.cp_obj, self.u_min, self.u_max);
// Project curve control points to plane perpendicular to ray // Project curve control points to plane perpendicular to ray
let mut dx = ray.d.cross(cp_obj[3] - cp_obj[0]); let mut dx = ray.d.cross(cp_obj[3] - cp_obj[0]);
@ -43,7 +116,6 @@ impl CurveShape {
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error"); let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error");
let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i])); let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
// Test ray against bound of projected control points
let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp( let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp(
self.u_max, self.u_max,
self.common.width[0], self.common.width[0],
@ -77,7 +149,7 @@ impl CurveShape {
let context = IntersectionContext { let context = IntersectionContext {
ray, ray,
object_from_ray: Arc::new(ray_from_object.inverse()), object_from_ray: ray_from_object.inverse(),
common: self.common.clone(), common: self.common.clone(),
}; };
@ -300,18 +372,18 @@ impl ShapeTrait for CurveShape {
} }
fn pdf(&self, _interaction: &Interaction) -> Float { fn pdf(&self, _interaction: &Interaction) -> Float {
todo!() unimplemented!()
} }
fn pdf_from_context(&self, _ctx: &ShapeSampleContext, _wi: Vector3f) -> Float { fn pdf_from_context(&self, _ctx: &ShapeSampleContext, _wi: Vector3f) -> Float {
todo!() unimplemented!()
} }
fn sample(&self, _u: Point2f) -> Option<ShapeSample> { fn sample(&self, _u: Point2f) -> Option<ShapeSample> {
todo!() unimplemented!()
} }
fn sample_from_context(&self, _ctx: &ShapeSampleContext, _u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, _ctx: &ShapeSampleContext, _u: Point2f) -> Option<ShapeSample> {
todo!() unimplemented!()
} }
} }

View file

@ -1,20 +1,39 @@
use super::{ use crate::core::geometry::{
Bounds3f, CylinderShape, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, Vector3fi, VectorLike,
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
}; };
use crate::core::geometry::{Sqrt, Tuple, VectorLike}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::interaction::InteractionTrait; use crate::core::shape::{
use crate::core::pbrt::gamma; QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
};
use crate::utils::splines::{
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
};
use crate::utils::transform::{Transform, look_at};
use crate::{Float, PI, gamma};
use crate::core::geometry::{Sqrt, Tuple};
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{difference_of_products, lerp, square}; use crate::utils::math::{clamp, difference_of_products, lerp, square};
use std::mem; use std::mem;
use std::sync::Arc;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CylinderShape {
pub radius: Float,
pub z_min: Float,
pub z_max: Float,
pub phi_max: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl CylinderShape { impl CylinderShape {
pub fn new( pub fn new(
render_from_object: Arc<Transform>, render_from_object: Transform,
object_from_render: Arc<Transform>, object_from_render: Transform,
reverse_orientation: bool, reverse_orientation: bool,
radius: Float, radius: Float,
z_min: Float, z_min: Float,
@ -26,7 +45,7 @@ impl CylinderShape {
z_min, z_min,
z_max, z_max,
phi_max, phi_max,
render_from_object: render_from_object.clone(), render_from_object,
object_from_render, object_from_render,
reverse_orientation, reverse_orientation,
transform_swap_handedness: render_from_object.swaps_handedness(), transform_swap_handedness: render_from_object.swaps_handedness(),
@ -247,14 +266,14 @@ impl ShapeTrait for CylinderShape {
(p_obj.z() - self.z_min) / (self.z_max - self.z_min), (p_obj.z() - self.z_min) / (self.z_max - self.z_min),
); );
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)), intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
pdf: 1. / self.area(), pdf: 1. / self.area(),
}) })
} }
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
let intr = Arc::make_mut(&mut ss.intr); let intr = &mut ss.intr;
intr.get_common_mut().time = ctx.time; intr.get_common_mut().time = ctx.time;
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {

View file

@ -1,22 +1,38 @@
use super::{ use crate::core::geometry::{
Bounds3f, DirectionCone, DiskShape, Float, Interaction, Normal3f, PI, Point2f, Point3f, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, Vector3fi, VectorLike,
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
}; };
use crate::core::geometry::VectorLike; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::interaction::InteractionTrait; use crate::core::shape::{
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
};
use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::{Float, PI};
use std::sync::Arc; use std::sync::Arc;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DiskShape {
pub radius: Float,
pub inner_radius: Float,
pub height: Float,
pub phi_max: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl DiskShape { impl DiskShape {
pub fn new( pub fn new(
radius: Float, radius: Float,
inner_radius: Float, inner_radius: Float,
height: Float, height: Float,
phi_max: Float, phi_max: Float,
render_from_object: Arc<Transform>, render_from_object: Transform,
object_from_render: Arc<Transform>, object_from_render: Transform,
reverse_orientation: bool, reverse_orientation: bool,
) -> Self { ) -> Self {
Self { Self {
@ -157,8 +173,9 @@ impl ShapeTrait for DiskShape {
phi / self.phi_max, phi / self.phi_max,
(self.radius - radius_sample) / (self.radius - self.inner_radius), (self.radius - radius_sample) / (self.radius - self.inner_radius),
); );
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)), intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
pdf: 1. / self.area(), pdf: 1. / self.area(),
}) })
} }
@ -173,17 +190,18 @@ impl ShapeTrait for DiskShape {
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> { fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
let intr = Arc::make_mut(&mut ss.intr); ss.intr.get_common_mut().time = ctx.time;
intr.get_common_mut().time = ctx.time;
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {
return None; return None;
} }
wi = wi.normalize(); wi = wi.normalize();
ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p()); ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
if ss.pdf.is_infinite() { if ss.pdf.is_infinite() {
return None; return None;
} }
Some(ss) Some(ss)
} }

View file

@ -5,315 +5,9 @@ pub mod disk;
pub mod sphere; pub mod sphere;
pub mod triangle; pub mod triangle;
use crate::core::geometry::{ pub use bilinear::*;
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f, pub use curves::*;
Vector3fi, VectorLike, pub use cylinder::*;
}; pub use disk::*;
use crate::core::interaction::{ pub use sphere::*;
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction, pub use triangle::*;
};
use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::{Float, PI};
use crate::lights::Light;
use crate::utils::math::{next_float_down, next_float_up};
use crate::utils::transform::Transform;
use enum_dispatch::enum_dispatch;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct SphereShape {
radius: Float,
z_min: Float,
z_max: Float,
theta_z_min: Float,
theta_z_max: Float,
phi_max: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
impl Default for SphereShape {
fn default() -> Self {
Self::new(
Transform::default().into(),
Transform::default().into(),
false,
1.0,
-1.0,
1.0,
360.0,
)
}
}
#[derive(Debug, Clone)]
pub struct CylinderShape {
radius: Float,
z_min: Float,
z_max: Float,
phi_max: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
#[derive(Debug, Clone)]
pub struct DiskShape {
radius: Float,
inner_radius: Float,
height: Float,
phi_max: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
#[derive(Debug, Clone)]
pub struct TriangleShape {
pub mesh_ind: usize,
pub tri_index: usize,
}
#[derive(Debug, Clone)]
pub struct BilinearPatchShape {
mesh_index: usize,
blp_index: usize,
area: Float,
rectangle: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CurveType {
Flat,
Cylinder,
Ribbon,
}
#[derive(Debug, Clone)]
pub struct CurveCommon {
curve_type: CurveType,
cp_obj: [Point3f; 4],
width: [Float; 2],
n: [Normal3f; 2],
normal_angle: Float,
inv_sin_normal_angle: Float,
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
transform_swap_handedness: bool,
}
impl CurveCommon {
#[allow(clippy::too_many_arguments)]
pub fn new(
c: &[Point3f],
w0: Float,
w1: Float,
curve_type: CurveType,
norm: &[Vector3f],
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
) -> Self {
let transform_swap_handedness = render_from_object.swaps_handedness();
let width = [w0, w1];
assert_eq!(c.len(), 4);
let cp_obj: [Point3f; 4] = c[..4].try_into().unwrap();
let mut n = [Normal3f::default(); 2];
let mut normal_angle: Float = 0.;
let mut inv_sin_normal_angle: Float = 0.;
if norm.len() == 2 {
n[0] = norm[0].normalize().into();
n[1] = norm[1].normalize().into();
normal_angle = n[0].angle_between(n[1]);
inv_sin_normal_angle = 1. / normal_angle.sin();
}
Self {
curve_type,
cp_obj,
width,
n,
normal_angle,
inv_sin_normal_angle,
render_from_object,
object_from_render,
reverse_orientation,
transform_swap_handedness,
}
}
}
#[derive(Debug, Clone)]
pub struct CurveShape {
common: CurveCommon,
u_min: Float,
u_max: Float,
}
// Define Intersection objects. This only varies for
#[derive(Debug, Clone)]
pub struct ShapeIntersection {
pub intr: SurfaceInteraction,
pub t_hit: Float,
}
impl ShapeIntersection {
pub fn new(intr: SurfaceInteraction, t_hit: Float) -> Self {
Self { intr, t_hit }
}
pub fn t_hit(&self) -> Float {
self.t_hit
}
pub fn set_t_hit(&mut self, new_t: Float) {
self.t_hit = new_t;
}
pub fn set_intersection_properties(
&mut self,
mtl: Arc<Material>,
area: Arc<Light>,
prim_medium_interface: Option<MediumInterface>,
ray_medium: Option<Arc<Medium>>,
) {
let ray_medium = ray_medium.expect("Ray medium must be defined for intersection");
self.intr
.set_intersection_properties(mtl, area, prim_medium_interface, ray_medium);
}
}
#[derive(Debug, Clone)]
pub struct QuadricIntersection {
t_hit: Float,
p_obj: Point3f,
phi: Float,
}
impl QuadricIntersection {
pub fn new(t_hit: Float, p_obj: Point3f, phi: Float) -> Self {
Self { t_hit, p_obj, phi }
}
}
#[derive(Debug, Clone, Copy)]
pub struct TriangleIntersection {
b0: Float,
b1: Float,
b2: Float,
t: Float,
}
impl TriangleIntersection {
pub fn new(b0: Float, b1: Float, b2: Float, t: Float) -> Self {
Self { b0, b1, b2, t }
}
}
#[derive(Debug, Clone)]
pub struct BilinearIntersection {
uv: Point2f,
t: Float,
}
impl BilinearIntersection {
pub fn new(uv: Point2f, t: Float) -> Self {
Self { uv, t }
}
}
#[derive(Clone)]
pub struct ShapeSample {
pub intr: Arc<SurfaceInteraction>,
pub pdf: Float,
}
#[derive(Clone, Debug)]
pub struct ShapeSampleContext {
pub pi: Point3fi,
pub n: Normal3f,
pub ns: Normal3f,
pub time: Float,
}
impl ShapeSampleContext {
pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f, time: Float) -> Self {
Self { pi, n, ns, time }
}
pub fn new_from_interaction(si: &SurfaceInteraction) -> Self {
Self {
pi: si.pi(),
n: si.n(),
ns: si.shading.n,
time: si.time(),
}
}
pub fn p(&self) -> Point3f {
Point3f::from(self.pi)
}
pub fn offset_ray_origin(&self, w: Vector3f) -> Point3f {
let d = self.n.abs().dot(self.pi.error().into());
let mut offset = d * Vector3f::from(self.n);
if w.dot(self.n.into()) < 0.0 {
offset = -offset;
}
let mut po = Point3f::from(self.pi) + offset;
for i in 0..3 {
if offset[i] > 0.0 {
po[i] = next_float_up(po[i]);
} else {
po[i] = next_float_down(po[i]);
}
}
po
}
pub fn offset_ray_origin_from_point(&self, pt: Point3f) -> Point3f {
self.offset_ray_origin(pt - self.p())
}
pub fn spawn_ray(&self, w: Vector3f) -> Ray {
Ray::new(self.offset_ray_origin(w), w, Some(self.time), None)
}
}
#[enum_dispatch]
pub trait ShapeTrait {
fn bounds(&self) -> Bounds3f;
fn normal_bounds(&self) -> DirectionCone;
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool;
fn area(&self) -> Float;
fn pdf(&self, interaction: &Interaction) -> Float;
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float;
fn sample(&self, u: Point2f) -> Option<ShapeSample>;
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample>;
}
#[derive(Debug, Clone)]
#[enum_dispatch(ShapeTrait)]
pub enum Shape {
Sphere(SphereShape),
Cylinder(CylinderShape),
Disk(DiskShape),
Triangle(TriangleShape),
BilinearPatch(BilinearPatchShape),
Curve(CurveShape),
}
impl Default for Shape {
fn default() -> Self {
Shape::Sphere(SphereShape::default())
}
}

View file

@ -1,22 +1,55 @@
use super::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f, Point3fi, Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, Vector3fi, VectorLike,
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
}; };
use crate::core::geometry::{Frame, Sqrt, VectorLike, spherical_direction}; use crate::core::geometry::{Frame, Sqrt, spherical_direction};
use crate::core::interaction::InteractionTrait; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::gamma; use crate::core::pbrt::gamma;
use crate::core::shape::{
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
};
use crate::utils::Transform;
use crate::utils::interval::Interval; use crate::utils::interval::Interval;
use crate::utils::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square}; use crate::utils::math::{clamp, difference_of_products, radians, safe_acos, safe_sqrt, square};
use crate::utils::sampling::sample_uniform_sphere; use crate::utils::sampling::sample_uniform_sphere;
use crate::{Float, PI};
use std::mem; use std::mem;
use std::sync::Arc; use std::sync::Arc;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SphereShape {
pub radius: Float,
pub z_min: Float,
pub z_max: Float,
pub theta_z_min: Float,
pub theta_z_max: Float,
pub phi_max: Float,
pub render_from_object: Transform,
pub object_from_render: Transform,
pub reverse_orientation: bool,
pub transform_swap_handedness: bool,
}
impl Default for SphereShape {
fn default() -> Self {
Self::new(
Transform::default().into(),
Transform::default().into(),
false,
1.0,
-1.0,
1.0,
360.0,
)
}
}
impl SphereShape { impl SphereShape {
pub fn new( pub fn new(
render_from_object: Arc<Transform>, render_from_object: Transform,
object_from_render: Arc<Transform>, object_from_render: Transform,
reverse_orientation: bool, reverse_orientation: bool,
radius: Float, radius: Float,
z_min: Float, z_min: Float,
@ -287,7 +320,7 @@ impl ShapeTrait for SphereShape {
)); ));
let si = SurfaceInteraction::new_simple(pi, n, uv); let si = SurfaceInteraction::new_simple(pi, n, uv);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(si), intr: Interaction::Surface(si),
pdf: 1. / self.area(), pdf: 1. / self.area(),
}) })
} }
@ -297,8 +330,7 @@ impl ShapeTrait for SphereShape {
let p_origin = ctx.offset_ray_origin_from_point(p_center); let p_origin = ctx.offset_ray_origin_from_point(p_center);
if p_origin.distance_squared(p_center) <= square(self.radius) { if p_origin.distance_squared(p_center) <= square(self.radius) {
let mut ss = self.sample(u)?; let mut ss = self.sample(u)?;
let intr = Arc::make_mut(&mut ss.intr); ss.intr.get_common_mut().time = ctx.time;
intr.get_common_mut().time = ctx.time;
let mut wi = ss.intr.p() - ctx.p(); let mut wi = ss.intr.p() - ctx.p();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {
return None; return None;
@ -349,9 +381,9 @@ impl ShapeTrait for SphereShape {
(theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min), (theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min),
); );
let pi = Point3fi::new_with_error(p_obj, p_error); let pi = Point3fi::new_with_error(p_obj, p_error);
let si = SurfaceInteraction::new_simple(pi, n, uv); let intr = SurfaceInteraction::new_simple(pi, n, uv);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(si), intr: Interaction::Surface(intr),
pdf: 1. / (2. * PI * one_minus_cos_theta_max), pdf: 1. / (2. * PI * one_minus_cos_theta_max),
}) })
} }

View file

@ -1,80 +1,130 @@
use super::{ use crate::Float;
Bounds3f, DirectionCone, Float, Interaction, Normal3f, Point2f, Point3f, Point3fi, Ray, use crate::core::geometry::{
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
TriangleIntersection, TriangleShape, Vector2f, Vector3f, Vector3f,
}; };
use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area}; use crate::core::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::InteractionTrait; use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
};
use crate::core::pbrt::gamma; use crate::core::pbrt::gamma;
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::math::{difference_of_products, square}; use crate::utils::math::{difference_of_products, square};
use crate::utils::mesh::TriangleMesh; use crate::utils::mesh::TriangleMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle, bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
sample_uniform_triangle, sample_uniform_triangle,
}; };
use std::mem;
use std::sync::{Arc, OnceLock};
pub static TRIANGLE_MESHES: OnceLock<Vec<Arc<TriangleMesh>>> = OnceLock::new(); #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct TriangleIntersection {
b0: Float,
b1: Float,
b2: Float,
t: Float,
}
#[derive(Clone, Copy)] impl TriangleIntersection {
struct TriangleData { pub fn new(b0: Float, b1: Float, b2: Float, t: Float) -> Self {
vertices: [Point3f; 3], Self { b0, b1, b2, t }
uvs: [Point2f; 3], }
normals: Option<[Normal3f; 3]>, }
area: Float,
normal: Normal3f, #[repr(C)]
reverse_orientation: bool, #[derive(Clone, Copy, Debug)]
transform_swaps_handedness: bool, pub struct TriangleShape {
pub mesh: TriangleMesh,
pub tri_index: u32,
} }
impl TriangleShape { impl TriangleShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22; pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
fn mesh(&self) -> &Arc<TriangleMesh> { #[inline(always)]
let meshes = TRIANGLE_MESHES fn get_vertex_indices(&self) -> [usize; 3] {
.get() unsafe {
.expect("Mesh has not been initialized"); let base_ptr = self
&meshes[self.mesh_ind] .mesh
} .vertex_indices
.0
fn get_data(&self) -> TriangleData { .add((self.tri_index as usize) * 3);
let mesh = self.mesh();
let start = 3 * self.tri_index;
let indices = &mesh.vertex_indices[start..start + 3];
let vertices = [mesh.p[indices[0]], mesh.p[indices[1]], mesh.p[indices[2]]];
let uvs = mesh.uv.as_ref().map_or(
[ [
Point2f::zero(), *base_ptr.add(0) as usize,
Point2f::new(1.0, 0.0), *base_ptr.add(1) as usize,
Point2f::new(1.0, 1.0), *base_ptr.add(2) as usize,
], ]
|uv| [uv[indices[0]], uv[indices[1]], uv[indices[2]]],
);
let normals = mesh
.n
.as_ref()
.map(|n| [n[indices[0]], n[indices[1]], n[indices[2]]]);
let dp1 = vertices[1] - vertices[0];
let dp2 = vertices[2] - vertices[0];
let normal = Normal3f::from(dp1.cross(dp2).normalize());
let area = 0.5 * dp1.cross(dp2).norm();
TriangleData {
vertices,
uvs,
normals,
area,
normal,
reverse_orientation: mesh.reverse_orientation,
transform_swaps_handedness: mesh.transform_swaps_handedness,
} }
} }
fn solid_angle(&self, p: Point3f) -> Float { #[inline(always)]
let data = self.get_data(); fn get_points(&self) -> [Point3f; 3] {
let [p0, p1, p2] = data.vertices; let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
[
*self.mesh.p.0.add(v0),
*self.mesh.p.0.add(v1),
*self.mesh.p.0.add(v2),
]
}
}
#[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 3]> {
if self.mesh.uv.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.uv.0.add(v0),
*self.mesh.uv.0.add(v1),
*self.mesh.uv.0.add(v2),
])
}
}
#[inline(always)]
fn get_tangents(&self) -> Option<[Vector3f; 3]> {
if self.mesh.s.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.s.0.add(v0),
*self.mesh.s.0.add(v1),
*self.mesh.s.0.add(v2),
])
}
}
#[inline(always)]
fn get_shading_normals(&self) -> Option<[Normal3f; 3]> {
if self.mesh.n.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.n.0.add(v0),
*self.mesh.n.0.add(v1),
*self.mesh.n.0.add(v2),
])
}
}
pub fn new(mesh: TriangleMesh, tri_index: u32) -> Self {
Self { mesh, tri_index }
}
pub fn get_mesh(&self) -> TriangleMesh {
self.mesh
}
pub fn solid_angle(&self, p: Point3f) -> Float {
let [p0, p1, p2] = self.get_points();
spherical_triangle_area( spherical_triangle_area(
(p0 - p).normalize(), (p0 - p).normalize(),
(p1 - p).normalize(), (p1 - p).normalize(),
@ -82,100 +132,15 @@ impl TriangleShape {
) )
} }
fn intersect_triangle(&self, ray: &Ray, t_max: Float) -> Option<TriangleIntersection> { fn intersect_triangle(
let data = self.get_data(); &self,
let [p0, p1, p2] = data.vertices; _ray: &Ray,
if (p2 - p0).cross(p1 - p0).norm_squared() == 0. { _t_max: Float,
return None; _p0: Point3f,
} _p1: Point3f,
let mut p0t = p0 - Vector3f::from(ray.o); _p2: Point3f,
let mut p1t = p1 - Vector3f::from(ray.o); ) -> Option<TriangleIntersection> {
let mut p2t = p2 - Vector3f::from(ray.o); todo!()
let kz = ray.d.abs().max_component_index();
let kx = if kz == 3 { 0 } else { kz + 1 };
let ky = if kz == 3 { 0 } else { kx + 1 };
let d = ray.d.permute([kx, ky, kz]);
p0t = p0t.permute([kx, ky, kz]);
p1t = p1t.permute([kx, ky, kz]);
p2t = p2t.permute([kx, ky, kz]);
// Apply shear transformation to translated vertex positions
let sx = -d.x() / d.z();
let sy = -d.y() / d.z();
let sz = 1. / d.z();
p0t[0] += sx * p0t.z();
p0t[1] += sy * p0t.z();
p1t[0] += sx * p1t.z();
p1t[1] += sy * p1t.z();
p2t[0] += sx * p2t.z();
p2t[0] += sy * p2t.z();
// Compute edge function coefficients e0, e1, and e2
let mut e0 = difference_of_products(p1t.x(), p2t.y(), p1t.y(), p2t.x());
let mut e1 = difference_of_products(p2t.x(), p0t.y(), p2t.y(), p0t.x());
let mut e2 = difference_of_products(p0t.x(), p1t.y(), p0t.y(), p1t.x());
// if mem::size_of::<Float>() == mem::size_of::<f32>() && (e0 == 0.0 || e1 == 0.0 || e2 == 0.0)
if e0 == 0.0 || e1 == 0.0 || e2 == 0.0 {
let [p0t64, p1t64, p2t64] = [p0t.cast::<f64>(), p1t.cast::<f64>(), p2t.cast::<f64>()];
e0 = (p2t64.y() * p1t64.x() - p2t64.x() * p1t64.y()) as Float;
e1 = (p0t64.y() * p2t64.x() - p0t64.x() * p2t64.y()) as Float;
e2 = (p1t64.y() * p0t64.x() - p1t64.x() * p0t64.y()) as Float;
}
if (e0 < 0. || e1 < 0. || e2 < 0.) && (e0 > 0. || e1 > 0. || e2 > 0.) {
return None;
}
let det = e0 + e1 + e2;
if det == 0. {
return None;
}
// Compute scaled hit distance to triangle and test against ray
p0t[2] *= sz;
p1t[2] *= sz;
p2t[2] *= sz;
let t_scaled = e0 * p0t.z() + e1 * p1t.z() + e2 * p2t.z();
if det < 0. && (t_scaled >= 0. || t_scaled < t_max * det)
|| (det > 0. && (t_scaled <= 0. || t_scaled > t_max * det))
{
return None;
}
// Compute barycentric coordinates and value for triangle intersection
let inv_det = 1. / det;
let b0 = e0 * inv_det;
let b1 = e1 * inv_det;
let b2 = e2 * inv_det;
let t = t_scaled * inv_det;
// Ensure that computed triangle is conservatively greater than zero
let max_z_t = Vector3f::new(p0t.z(), p1t.z(), p2t.z())
.abs()
.max_component_value();
let delta_z = gamma(3) * max_z_t;
let max_x_t = Vector3f::new(p0t.x(), p1t.x(), p2t.x())
.abs()
.max_component_value();
let max_y_t = Vector3f::new(p0t.y(), p1t.y(), p2t.y())
.abs()
.max_component_value();
let delta_x = gamma(5) * (max_x_t + max_z_t);
let delta_y = gamma(5) * (max_y_t + max_z_t);
let delta_e = 2. * (gamma(2) * max_x_t * max_y_t + delta_y * max_x_t + delta_x * max_y_t);
let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value();
let delta_t =
3. * (gamma(3) * max_e * max_z_t + delta_e * max_z_t + delta_z * max_e) * inv_det.abs();
if t <= delta_t {
return None;
}
Some(TriangleIntersection::new(b0, b1, b2, t))
} }
fn interaction_from_intersection( fn interaction_from_intersection(
@ -184,233 +149,255 @@ impl TriangleShape {
time: Float, time: Float,
wo: Vector3f, wo: Vector3f,
) -> SurfaceInteraction { ) -> SurfaceInteraction {
let data = self.get_data(); let [p0, p1, p2] = self.get_points();
let [p0, p1, p2] = data.vertices; let uv = self.get_uvs().unwrap_or([
let [uv0, uv1, uv2] = data.uvs; Point2f::new(0.0, 0.0),
// Compute triangle partial derivatives Point2f::new(1.0, 0.0),
let (dpdu, dpdv, degenerate_uv, det) = self.compute_partials(data); Point2f::new(1.0, 1.0),
// Interpolate (u, v) parametric coordinates and hit point ]);
let p_hit_vec = let duv02 = uv[0] - uv[2];
ti.b0 * Vector3f::from(p0) + ti.b1 * Vector3f::from(p1) + ti.b2 * Vector3f::from(p2); let duv12 = uv[1] - uv[2];
let p_hit = Point3f::from(p_hit_vec); let dp02 = p0 - p2;
let uv_hit_vec = let dp12 = p1 - p2;
ti.b0 * Vector2f::from(uv0) + ti.b1 * Vector2f::from(uv1) + ti.b2 * Vector2f::from(uv2); let determinant = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]);
let uv_hit = Point2f::from(uv_hit_vec); let degenerate = determinant.abs() < 1e-9;
let (mut dpdu, mut dpdv) = if !degenerate {
let invdet = 1. / determinant;
let ret0 = difference_of_products(duv12[1], dp02, duv02[1], dp12) * invdet;
let ret1 = difference_of_products(duv02[0], dp12, duv12[0], dp02) * invdet;
(ret0, ret1)
} else {
(Vector3f::zero(), Vector3f::zero())
};
// Return SurfaceInteraction for triangle hit> if degenerate || dpdu.cross(dpdv).norm_squared() == 0. {
let flip_normal = data.reverse_orientation ^ data.transform_swaps_handedness; let mut ng = (p2 - p0).cross(p1 - p0);
let p_abs_sum = (ti.b0 * Vector3f::from(p0)).abs() if ng.norm_squared() == 0. {
+ (ti.b1 * Vector3f::from(p1)).abs() let v1 = p2 - p0;
+ (ti.b2 * Vector3f::from(p2)).abs(); let v2 = p1 - p0;
ng = v1.cast::<f64>().cross(v2.cast::<f64>()).cast::<Float>();
assert!(ng.norm_squared() != 0.);
}
(dpdu, dpdv) = ng.normalize().coordinate_system();
}
let p0_vec = Vector3f::from(p0);
let p1_vec = Vector3f::from(p1);
let p2_vec = Vector3f::from(p2);
let p_hit = Point3f::from(ti.b0 * p0_vec + ti.b1 * p1_vec + ti.b2 * p2_vec);
let uv_hit = Point2f::from(
ti.b0 * Vector2f::from(uv[0])
+ ti.b1 * Vector2f::from(uv[1])
+ ti.b2 * Vector2f::from(uv[2]),
);
let p_abs_sum = (ti.b0 * p0_vec).abs() + (ti.b1 * p1_vec).abs() + (ti.b2 * p2_vec).abs();
let p_error = gamma(7) * p_abs_sum; let p_error = gamma(7) * p_abs_sum;
let mut ng = Normal3f::from(dp02.cross(dp12).normalize());
let flip_normal = self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness;
if flip_normal {
ng = -ng;
}
let mut isect = SurfaceInteraction::new( let mut isect = SurfaceInteraction::new(
Point3fi::new_with_error(p_hit, p_error), Point3fi::new_with_error(p_hit, p_error),
uv_hit, uv_hit,
wo, wo,
dpdu, dpdu,
dpdv, dpdv,
Normal3f::default(), Normal3f::zero(),
Normal3f::default(), Normal3f::zero(),
time, time,
flip_normal, flip_normal,
); );
isect.face_index = self isect.face_index = if !self.mesh.face_indices.is_null() {
.mesh() unsafe { *self.mesh.face_indices.0.add(self.tri_index as usize) }
.face_indices } else {
.as_ref() 0
.map_or(0, |fi| fi[self.tri_index]); };
isect.common.n = data.normal;
isect.shading.n = isect.n();
if flip_normal {
isect.common.n = -isect.n();
isect.shading.n = -isect.shading.n;
}
if data.normals.is_some() || self.mesh().s.is_some() { isect.common.n = ng;
self.apply_shading_normals(&mut isect, ti, data, degenerate_uv, det); isect.shading.n = ng;
}
if !self.mesh.p.is_null() || !self.mesh.s.is_null() {
self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate);
}
isect isect
} }
fn compute_partials(&self, data: TriangleData) -> (Vector3f, Vector3f, bool, Float) { fn compute_shading_geometry(
let [p0, p1, p2] = data.vertices;
let [uv0, uv1, uv2] = data.uvs;
let duv02 = uv0 - uv2;
let duv12 = uv1 - uv2;
let dp02 = p0 - p2;
let dp12 = p1 - p2;
let det = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]);
let degenerate_uv = det.abs() < 1e-9;
let (dpdu, dpdv) = if !degenerate_uv {
let inv_det = 1. / det;
(
(dp02 * duv12[1] - dp12 * duv02[1]) * inv_det,
(dp12 * duv02[0] - dp02 * duv12[0]) * inv_det,
)
} else {
let dp20 = p2 - p0;
let dp10 = p1 - p0;
let mut ng = dp20.cross(dp10);
if ng.norm_squared() == 0. {
ng = (dp20.cast::<f64>().cross(dp10.cast::<f64>())).cast();
}
let n = ng.normalize();
n.coordinate_system()
};
(dpdu, dpdv, degenerate_uv, det)
}
fn apply_shading_normals(
&self, &self,
isect: &mut SurfaceInteraction, isect: &mut SurfaceInteraction,
ti: TriangleIntersection, ti: &TriangleIntersection,
data: TriangleData, uv: [Point2f; 3],
dpdu_geom: Vector3f,
determinant: Float,
degenerate_uv: bool, degenerate_uv: bool,
det: Float,
) { ) {
let Some([n0, n1, n2]) = data.normals else { // Interpolate vertex normals if they exist
return; let ns = if let Some(normals) = self.get_shading_normals() {
}; let n = ti.b0 * normals[0] + ti.b1 * normals[1] + ti.b2 * normals[2];
let [uv0, uv1, uv2] = data.uvs; if n.norm_squared() > 0.0 {
let duv02 = uv0 - uv2; n.normalize()
let duv12 = uv1 - uv2; } else {
isect.n()
let ns = ti.b0 * n0 + ti.b1 * n1 + ti.b2 * n2; }
let ns = if ns.norm_squared() > 0. {
ns.normalize()
} else { } else {
isect.n() isect.n()
}; };
let mut ss = self.mesh().s.as_ref().map_or(isect.dpdu, |s| { // Interpolate tangents if they exist
let indices = &self.mesh().vertex_indices[3 * self.tri_index..3 * self.tri_index + 3]; let mut ss = if let Some(tangents) = self.get_tangents() {
let interp_s = ti.b0 * s[indices[0]] + ti.b1 * s[indices[1]] + ti.b2 * s[indices[2]]; let s = ti.b0 * tangents[0] + ti.b1 * tangents[1] + ti.b2 * tangents[2];
if s.norm_squared() > 0.0 {
if interp_s.norm_squared() > 0. { s.normalize()
interp_s
} else { } else {
isect.dpdu dpdu_geom
}
});
let mut ts = Vector3f::from(ns).cross(ss);
if ts.norm_squared() > 0. {
ss = ts.cross(Vector3f::from(ns));
} else {
(ss, ts) = Vector3f::from(ns).coordinate_system();
}
let (dndu, dndv) = if degenerate_uv {
let dn = (n2 - n0).cross(n1 - n0);
if dn.norm_squared() == 0. {
(Normal3f::zero(), Normal3f::zero())
} else {
dn.coordinate_system()
} }
} else { } else {
let inv_det = 1. / det; dpdu_geom
let dn02 = n0 - n2;
let dn12 = n1 - n2;
(
(dn02 * duv12[1] - dn12 * duv02[1]) * inv_det,
(dn12 * duv02[0] - dn02 * duv12[0]) * inv_det,
)
}; };
isect.shading.n = ns; // Ensure shading tangent (ss) is perpendicular to shading normal (ns)
isect.shading.dpdu = ss; let mut ts = ns.cross(ss.into());
isect.shading.dpdv = ts; if ts.norm_squared() > 0.0 {
isect.dndu = dndu; ss = ts.cross(ns.into()).into();
isect.dndv = dndv; } else {
let (s, t) = ns.coordinate_system();
ss = s.into();
ts = t.into();
}
// How does the normal change as we move across UVs?
let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() {
if degenerate_uv {
let dn = (normals[2] - normals[0]).cross(normals[1] - normals[0]);
if dn.norm_squared() == 0.0 {
(Normal3f::zero(), Normal3f::zero())
} else {
let (dnu, dnv) = dn.coordinate_system();
(Normal3f::from(dnu), Normal3f::from(dnv))
}
} else {
let dn1 = normals[0] - normals[2];
let dn2 = normals[1] - normals[2];
let duv02 = uv[0] - uv[2];
let duv12 = uv[1] - uv[2];
let inv_det = 1.0 / determinant;
(
difference_of_products(duv12[1], dn1, duv02[1], dn2) * inv_det,
difference_of_products(duv02[0], dn2, duv12[0], dn1) * inv_det,
)
}
} else {
(Normal3f::zero(), Normal3f::zero())
};
isect.set_shading_geom(ns, ss, ts.into(), dndu, dndv, true);
} }
} }
impl ShapeTrait for TriangleShape { impl ShapeTrait for TriangleShape {
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
let [p0, p1, p2] = self.get_data().vertices; let [p0, p1, p2] = self.get_points();
Bounds3f::from_points(p0, p1).union_point(p2) Bounds3f::from_points(p0, p1).union_point(p2)
} }
fn normal_bounds(&self) -> DirectionCone { fn normal_bounds(&self) -> DirectionCone {
let data = self.get_data(); let [p0, p1, p2] = self.get_points();
let mut n = data.normal; let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into();
if let Some([n0, n1, n2]) = data.normals {
n = n.face_forward((n0 + n1 + n2).into()); if let Some(normals) = self.get_shading_normals() {
} else if data.reverse_orientation ^ data.transform_swaps_handedness { let [n0, n1, n2] = normals;
let ns = n0 + n1 + n2;
n = n.face_forward(ns);
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n = -n; n = -n;
} }
DirectionCone::new_from_vector(Vector3f::from(n))
DirectionCone::new_from_vector(n.into())
} }
fn area(&self) -> Float { fn area(&self) -> Float {
self.get_data().area let [p0, p1, p2] = self.get_points();
0.5 * (p1 - p0).cross(p2 - p0).norm()
} }
fn pdf(&self, _interaction: &Interaction) -> Float { fn sample(&self, u: Point2f) -> Option<ShapeSample> {
1. / self.area() let [p0, p1, p2] = self.get_points();
let b = sample_uniform_triangle(u);
let p = p0 + b[1] * (p1 - p0) + b[2] * (p2 - p0);
let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into();
if let Some(normals) = self.get_shading_normals() {
let [n0, n1, n2] = normals;
let ns = b[0] * n0 + b[1] * n1 + b[2] * n2; // b[2] is (1 - b0 - b1)
n = n.face_forward(ns);
} else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
n = -n;
}
let uv_sample = if let Some(uvs) = self.get_uvs() {
let [uv0, uv1, uv2] = uvs;
uv0 + b[1] * (uv1 - uv0) + b[2] * (uv2 - uv0)
} else {
let v = b[0] * Vector2f::new(0.0, 0.0)
+ b[1] * Vector2f::new(1.0, 0.0)
+ b[2] * Vector2f::new(1.0, 1.0);
Point2f::from(v)
};
let p0_v = Vector3f::from(p0);
let p1_v = Vector3f::from(p1);
let p2_v = Vector3f::from(p2);
let p_abs_sum = (b[0] * p0_v).abs() + (b[1] * p1_v).abs() + (b[2] * p2_v).abs();
let p_error = Vector3f::from(p_abs_sum) * gamma(6);
let intr_base = InteractionBase::new_surface_geom(
Point3fi::new_with_error(p, p_error),
n,
uv_sample,
Vector3f::default(),
0.,
);
Some(ShapeSample {
intr: Interaction::Simple(SimpleInteraction::new(intr_base)),
pdf: 1.0 / self.area(),
})
}
fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option<ShapeSample> {
let [p0, p1, p2] = self.get_points();
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?;
if tri_pdf == 0. {
return None;
} }
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
let solid_angle = self.solid_angle(ctx.p()); let solid_angle = self.solid_angle(ctx.p());
if (Self::MIN_SPHERICAL_SAMPLE_AREA..Self::MAX_SPHERICAL_SAMPLE_AREA).contains(&solid_angle) if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
{ {
let ray = ctx.spawn_ray(wi); let mut ss = self.sample(u)?;
return self.intersect(&ray, None).map_or(0., |isect| { ss.intr.get_common_mut().time = ctx.time;
let absdot = Vector3f::from(isect.intr.n()).dot(-wi).abs(); let mut wi: Normal3f = (ss.intr.p() - ctx.p()).into();
let d2 = ctx.p().distance_squared(isect.intr.p());
let pdf = 1. / self.area() * (d2 / absdot);
if pdf.is_infinite() { 0. } else { pdf }
});
}
let mut pdf = 1. / solid_angle;
if ctx.ns != Normal3f::zero() {
let [p0, p1, p2] = self.get_data().vertices;
let u = invert_spherical_triangle_sample(&[p0, p1, p2], ctx.p(), wi)
.unwrap_or(Point2f::zero());
let rp = ctx.p();
let wi: [Vector3f; 3] = [
(p0 - rp).normalize(),
(p1 - rp).normalize(),
(p2 - rp).normalize(),
];
let w: [Float; 4] = [
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()),
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()),
];
pdf *= bilinear_pdf(u, &w);
}
pdf
}
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data();
let [p0, p1, p2] = data.vertices;
let solid_angle = self.solid_angle(ctx.p());
if (Self::MIN_SPHERICAL_SAMPLE_AREA..Self::MAX_SPHERICAL_SAMPLE_AREA).contains(&solid_angle)
{
// Sample shape by area and compute incident direction wi
return self.sample(u).and_then(|mut ss| {
let mut intr_clone = (*ss.intr).clone();
intr_clone.common.time = ctx.time;
ss.intr = Arc::new(intr_clone);
let wi = (ss.intr.p() - ctx.p()).normalize();
if wi.norm_squared() == 0. { if wi.norm_squared() == 0. {
return None; return None;
} }
let absdot = Vector3f::from(ss.intr.n()).abs_dot(-wi); wi = wi.normalize();
let d2 = ctx.p().distance_squared(ss.intr.p()); ss.pdf /= ss.intr.n().abs_dot(-wi) / ctx.p().distance_squared(ss.intr.p());
ss.pdf /= absdot / d2; if ss.pdf.is_infinite() {
if ss.pdf.is_infinite() { None } else { Some(ss) } return None;
}); }
return Some(ss);
} }
// Sample spherical triangle from reference point
let mut pdf = 1.; let mut pdf = 1.;
if ctx.ns != Normal3f::zero() { if ctx.ns != Normal3f::zero() {
let rp = ctx.p(); let rp = ctx.p();
@ -420,93 +407,112 @@ impl ShapeTrait for TriangleShape {
(p2 - rp).normalize(), (p2 - rp).normalize(),
]; ];
let w: [Float; 4] = [ let w: [Float; 4] = [
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()), ctx.ns.abs_dot(wi[1].into()).max(0.01),
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()), ctx.ns.abs_dot(wi[1].into()).max(0.01),
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()), ctx.ns.abs_dot(wi[0].into()).max(0.01),
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()), ctx.ns.abs_dot(wi[2].into()).max(0.01),
]; ];
u = sample_bilinear(u, &w);
let u = sample_bilinear(u, &w);
pdf = bilinear_pdf(u, &w); pdf = bilinear_pdf(u, &w);
} }
let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?; let p0_v = Vector3f::from(p0);
if tri_pdf == 0. { let p1_v = Vector3f::from(p1);
return None; let p2_v = Vector3f::from(p2);
} let p_abs_sum = (b[0] * p0_v).abs() + (b[1] * p1_v).abs() + (b[2] * p2_v).abs();
pdf *= tri_pdf; let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into();
let b2 = 1. - b[0] - b[1];
let p_abs_sum = b[0] * Vector3f::from(p0) if let Some(normals) = self.get_shading_normals() {
+ b[1] * Vector3f::from(p1) let [n0, n1, n2] = normals;
+ (1. - b[0] - b[1]) * Vector3f::from(p2); let ns = b[0] * n0 + b[1] * n1 + (1. - b[0] - b[1]) * n2;
let p_error = gamma(6) * p_abs_sum; n = n.face_forward(ns);
// Return ShapeSample for solid angle sampled point on triangle } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness {
let p_vec =
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
let p = Point3f::from(p_vec);
let mut n = Normal3f::from((p1 - p0).cross(p2 - p0).normalize());
if let Some([n0, n1, n2]) = data.normals {
let ns = b[0] * n0 + b[1] * n1 + b2 * n2;
n = n.face_forward(ns.into());
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
n = -n; n = -n;
} }
let [uv0, uv1, uv2] = data.uvs; let uv_sample = if let Some(uvs) = self.get_uvs() {
let uv_sample_vec = let [uv0, uv1, uv2] = uvs;
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2); uv0 + b[1] * (uv1 - uv0) + b[2] * (uv2 - uv0)
let uv_sample = Point2f::from(uv_sample_vec); } else {
let pi = Point3fi::new_with_error(p, p_error); let v = b[0] * Vector2f::new(0.0, 0.0)
let mut si = SurfaceInteraction::new_simple(pi, n, uv_sample); + b[1] * Vector2f::new(1.0, 0.0)
si.common.time = ctx.time; + b[2] * Vector2f::new(1.0, 1.0);
Point2f::from(v)
};
let p = p0 + b[1] * (p1 - p0) + b[2] * (p2 - p0);
let p_error = Vector3f::from(p_abs_sum) * gamma(6);
let intr_base = InteractionBase::new_surface_geom(
Point3fi::new_with_error(p, p_error),
n,
uv_sample,
Vector3f::default(),
0.,
);
Some(ShapeSample { Some(ShapeSample {
intr: Arc::new(si), intr: Interaction::Simple(SimpleInteraction::new(intr_base)),
pdf, pdf,
}) })
} }
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
let data = self.get_data();
let [p0, p1, p2] = data.vertices;
let [uv0, uv1, uv2] = data.uvs;
let b = sample_uniform_triangle(u);
let p_vec =
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
let b2 = 1. - b[0] - b[1];
let p = Point3f::from(p_vec);
let mut n = data.normal;
if let Some([n0, n1, n2]) = data.normals {
let interp_n = b[0] * n0 + b[1] * n1 + b2 * n2;
n = n.face_forward(interp_n.into());
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
n = -n;
}
let uv_sample_vec =
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2);
let uv_sample = Point2f::from(uv_sample_vec);
let p_abs_sum = (b[0] * Vector3f::from(p0)).abs()
+ (b[1] * Vector3f::from(p1)).abs()
+ ((1. - b[0] - b[1]) * Vector3f::from(p2)).abs();
let p_error = gamma(6) * p_abs_sum;
let pi = Point3fi::new_with_error(p, p_error);
Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv_sample)),
pdf: 1. / self.area(),
})
}
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY)) let [p0, p1, p2] = self.get_points();
.map(|ti| { let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(0.), p0, p1, p2)?;
let intr = self.interaction_from_intersection(ti, ray.time, -ray.d); let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d);
ShapeIntersection { intr, t_hit: ti.t } Some(ShapeIntersection::new(intr, tri_isect.t))
})
} }
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY)) let [p0, p1, p2] = self.get_points();
.is_some() let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(0.), p0, p1, p2);
tri_isect.is_some()
}
fn pdf(&self, _interaction: &Interaction) -> Float {
1. / self.area()
}
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
let solid_angle = self.solid_angle(ctx.p());
if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
{
let ray = ctx.spawn_ray(wi);
let Some(isect) = self.intersect(&ray, None) else {
return 0.;
};
let pdf = (1. / self.area())
/ (isect.intr.n().abs_dot(-Normal3f::from(wi))
/ ctx.p().distance_squared(isect.intr.p()));
if pdf.is_infinite() {
return 0.;
}
return pdf;
}
let mut pdf = 1. / solid_angle;
if ctx.ns != Normal3f::zero() {
let [p0, p1, p2] = self.get_points();
let u = invert_spherical_triangle_sample(&[p0, p1, p2], ctx.p(), wi)
.expect("Could not calculate inverse sample");
let rp = ctx.p();
let wi = [
(p0 - rp).normalize(),
(p1 - rp).normalize(),
(p2 - rp).normalize(),
];
let w: [Float; 4] = [
ctx.ns.abs_dot(wi[1].into()).max(0.01),
ctx.ns.abs_dot(wi[1].into()).max(0.01),
ctx.ns.abs_dot(wi[0].into()).max(0.01),
ctx.ns.abs_dot(wi[2].into()).max(0.01),
];
pdf *= bilinear_pdf(u, &w);
}
pdf
} }
} }

View file

@ -1462,7 +1462,7 @@ pub const CIE_D65: [Float; 95] = [
N!(115.392), N!(115.392),
N!(115.923), N!(115.923),
N!(112.367), N!(112.367),
N(108.811), N!(108.811),
N!(109.082), N!(109.082),
N!(109.354), N!(109.354),
N!(108.578), N!(108.578),

View file

@ -3,31 +3,28 @@ use crate::core::geometry::Point2f;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
use crate::utils::math::SquareMatrix3f; use crate::utils::math::SquareMatrix3f;
use crate::utils::ptr::Ptr;
use once_cell::sync::Lazy;
use std::cmp::{Eq, PartialEq}; use std::cmp::{Eq, PartialEq};
use std::error::Error;
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Debug, Clone)]
pub struct StandardColorSpaces { pub struct StandardColorSpaces {
pub srgb: *const RGBColorSpace, pub srgb: Ptr<RGBColorSpace>,
pub dci_p3: *const RGBColorSpace, pub dci_p3: Ptr<RGBColorSpace>,
pub rec2020: *const RGBColorSpace, pub rec2020: Ptr<RGBColorSpace>,
pub aces2065_1: *const RGBColorSpace, pub aces2065_1: Ptr<RGBColorSpace>,
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct RGBColorSpace { pub struct RGBColorSpace {
pub r: Point2f, pub r: Point2f,
pub g: Point2f, pub g: Point2f,
pub b: Point2f, pub b: Point2f,
pub w: Point2f, pub w: Point2f,
pub illuminant: DenselySampledSpectrum, pub illuminant: DenselySampledSpectrum,
pub rgb_to_spectrum_table: *const RGBToSpectrumTable, pub rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
pub xyz_from_rgb: SquareMatrix3f, pub xyz_from_rgb: SquareMatrix3f,
pub rgb_from_xyz: SquareMatrix3f, pub rgb_from_xyz: SquareMatrix3f,
} }

View file

@ -6,7 +6,7 @@ pub mod simple;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
pub use colorspace::RGBColorSpace; pub use colorspace::{RGBColorSpace, StandardColorSpaces};
pub use rgb::*; pub use rgb::*;
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN}; pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};

View file

@ -4,6 +4,7 @@ use super::{
}; };
use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ}; use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::utils::Ptr;
use crate::Float; use crate::Float;
@ -68,13 +69,12 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
pub struct RGBIlluminantSpectrum { pub struct RGBIlluminantSpectrum {
pub scale: Float, pub scale: Float,
pub rsp: RGBSigmoidPolynomial, pub rsp: RGBSigmoidPolynomial,
pub illuminant: DenselySampledSpectrum, pub illuminant: Ptr<DenselySampledSpectrum>,
} }
impl RGBIlluminantSpectrum { impl RGBIlluminantSpectrum {
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self { pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
let illuminant = &cs.illuminant; let illuminant = cs.illuminant;
let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
let m = rgb.max_component_value(); let m = rgb.max_component_value();
let scale = 2. * m; let scale = 2. * m;
let rsp = cs.to_rgb_coeffs(if scale == 1. { let rsp = cs.to_rgb_coeffs(if scale == 1. {
@ -85,33 +85,31 @@ impl RGBIlluminantSpectrum {
Self { Self {
scale, scale,
rsp, rsp,
illuminant, illuminant: Ptr::from(&illuminant),
} }
} }
} }
impl SpectrumTrait for RGBIlluminantSpectrum { impl SpectrumTrait for RGBIlluminantSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
match &self.illuminant { if self.illuminant.is_null() {
Some(illuminant) => { return 0.;
self.scale * self.rsp.evaluate(lambda) * illuminant.evaluate(lambda)
}
None => 0.0,
} }
self.scale * self.rsp.evaluate(lambda) * self.illuminant.evaluate(lambda)
} }
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
if self.illuminant.is_none() { if self.illuminant.is_null() {
return SampledSpectrum::new(0.); return SampledSpectrum::new(0.);
} }
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i])) SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
match &self.illuminant { if self.illuminant.is_null() {
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(), return 0.;
None => 0.0,
} }
self.scale * self.rsp.max_value() * self.illuminant.max_value()
} }
} }

View file

@ -1,5 +1,5 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::StandardSpectra; use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
use crate::utils::math::{clamp, lerp}; use crate::utils::math::{clamp, lerp};
use std::ops::{ use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
@ -118,7 +118,7 @@ impl SampledSpectrum {
} }
pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float { pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float {
let ys = std.cie_y().sample(lambda); let ys = std.y.sample(lambda);
let pdf = lambda.pdf(); let pdf = lambda.pdf();
SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL
} }
@ -305,7 +305,7 @@ pub struct SampledWavelengths {
impl SampledWavelengths { impl SampledWavelengths {
pub fn pdf(&self) -> SampledSpectrum { pub fn pdf(&self) -> SampledSpectrum {
SampledSpectrum::from_vector(self.pdf.to_vec()) SampledSpectrum::from_array(&self.pdf)
} }
pub fn secondary_terminated(&self) -> bool { pub fn secondary_terminated(&self) -> bool {

View file

@ -1,8 +1,9 @@
use super::cie::*; use super::cie::*;
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
use crate::utils::ptr::Ptr;
use crate::{Float, find_interval};
use core::slice; use core::slice;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::LazyLock; use std::sync::LazyLock;
@ -34,7 +35,7 @@ impl SpectrumTrait for ConstantSpectrum {
pub struct DenselySampledSpectrum { pub struct DenselySampledSpectrum {
pub lambda_min: i32, pub lambda_min: i32,
pub lambda_max: i32, pub lambda_max: i32,
pub values: *const Float, pub values: Ptr<Float>,
} }
unsafe impl Send for DenselySampledSpectrum {} unsafe impl Send for DenselySampledSpectrum {}
@ -42,148 +43,155 @@ unsafe impl Sync for DenselySampledSpectrum {}
impl DenselySampledSpectrum { impl DenselySampledSpectrum {
#[inline(always)] #[inline(always)]
fn as_slice(&self) -> &[Float] { pub fn count(&self) -> usize {
if self.values.is_null() { if self.values.is_null() {
return &[]; 0
}
let len = (self.lambda_max - self.lambda_min + 1).max(0) as usize;
unsafe { slice::from_raw_parts(self.values, len) }
}
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as i32 - self.lambda_min;
let len = (self.lambda_max - self.lambda_min + 1) as i32;
if offset < 0 || offset >= len {
s[i] = 0.0;
} else { } else {
unsafe { s[i] = *self.values.add(offset as usize) }; (self.lambda_max - self.lambda_min + 1) as usize
} }
} }
s
}
pub fn min_component_value(&self) -> Float { #[inline(always)]
self.as_slice() fn get(&self, idx: u32) -> Float {
.iter() unsafe { *self.values.0.add(idx as usize) }
.fold(Float::INFINITY, |a, &b| a.min(b))
}
pub fn max_component_value(&self) -> Float {
self.as_slice()
.iter()
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
}
pub fn average(&self) -> Float {
let slice = self.as_slice();
if slice.is_empty() {
return 0.0;
}
slice.iter().sum::<Float>() / (slice.len() as Float)
}
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
let mut r = Self::new(1, 1);
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
} }
} }
impl PartialEq for DenselySampledSpectrum { impl PartialEq for DenselySampledSpectrum {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
if self.lambda_min != other.lambda_min self.lambda_min == other.lambda_min
|| self.lambda_max != other.lambda_max && self.lambda_max == other.lambda_max
|| self.values.len() != other.values.len() && self.values.0 == other.values.0
{
return false;
}
self.values
.iter()
.zip(&other.values)
.all(|(a, b)| a.to_bits() == b.to_bits())
} }
} }
impl Eq for DenselySampledSpectrum {} impl Eq for DenselySampledSpectrum {}
impl Hash for DenselySampledSpectrum { // impl Hash for DenselySampledSpectrum {
fn hash<H: Hasher>(&self, state: &mut H) { // fn hash<H: Hasher>(&self, state: &mut H) {
self.lambda_min.hash(state); // self.lambda_min.hash(state);
self.lambda_max.hash(state); // self.lambda_max.hash(state);
//
for v in &self.values { // for v in &self.values {
v.to_bits().hash(state); // v.to_bits().hash(state);
} // }
} // }
} // }
impl SpectrumTrait for DenselySampledSpectrum { impl SpectrumTrait for DenselySampledSpectrum {
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
let n = self.count() as i32;
for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as i32 - self.lambda_min;
if offset < 0 || offset >= n {
s[i] = 0.0;
} else {
unsafe {
s[i] = *self.values.0.add(offset as usize);
}
}
}
s
}
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
let offset = (lambda.round() as i32) - self.lambda_min; let offset = (lambda.round() as i32) - self.lambda_min;
if offset < 0 || offset as usize >= self.values.len() { let n = self.count() as i32;
if offset < 0 || offset >= n {
0.0 0.0
} else { } else {
self.values[offset as usize] unsafe { *self.values.0.add(offset as usize) }
} }
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
self.values.iter().fold(Float::MIN, |a, b| a.max(*b)) if self.values.is_null() {
return 0.;
}
let n = self.count();
let mut max_val = Float::NEG_INFINITY;
for i in 0..n {
unsafe {
let val = *self.values.0.add(i);
if val > max_val {
max_val = val;
}
}
}
max_val
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct PiecewiseLinearSpectrum { pub struct PiecewiseLinearSpectrum {
pub lambdas: *const Float, pub lambdas: Ptr<Float>,
pub values: *const Float, pub values: Ptr<Float>,
pub count: u32, pub count: u32,
} }
impl PiecewiseLinearSpectrum {
#[inline(always)]
fn lambda(&self, i: u32) -> Float {
unsafe { *self.lambdas.0.add(i as usize) }
}
#[inline(always)]
fn value(&self, i: u32) -> Float {
unsafe { *self.values.0.add(i as usize) }
}
}
unsafe impl Send for PiecewiseLinearSpectrum {} unsafe impl Send for PiecewiseLinearSpectrum {}
unsafe impl Sync for PiecewiseLinearSpectrum {} unsafe impl Sync for PiecewiseLinearSpectrum {}
impl SpectrumTrait for PiecewiseLinearSpectrum { impl SpectrumTrait for PiecewiseLinearSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
if self.lambdas.is_empty() { if self.lambdas.is_null() {
return 0.0; return 0.0;
} }
if lambda <= self.lambdas[0] { if lambda <= self.lambda(0) {
return self.values[0]; return self.value(0);
} }
if lambda >= *self.lambdas.last().unwrap() { if lambda >= self.lambda(self.count - 1) {
return *self.values.last().unwrap(); return self.value(self.count - 1);
} }
let i = self.lambdas.partition_point(|&l| l < lambda); let i = find_interval(self.count, |idx| self.lambda(idx) <= lambda);
let l0 = self.lambdas[i - 1];
let l1 = self.lambdas[i]; let l0 = self.lambda(i);
let v0 = self.values[i - 1]; let l1 = self.lambda(i + 1);
let v1 = self.values[i]; let v0 = self.value(i);
let v1 = self.value(i + 1);
let t = (lambda - l0) / (l1 - l0); let t = (lambda - l0) / (l1 - l0);
v0 + t * (v1 - v0) v0 + t * (v1 - v0)
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
if self.values.is_empty() { if self.values.is_null() {
return 0.0; return 0.;
} }
self.values.iter().fold(0.0, |acc, &v| acc.max(v))
let n = self.count;
let mut max_val = Float::NEG_INFINITY;
for i in 0..n {
unsafe {
let val = *self.values.0.add(i as usize);
if val > max_val {
max_val = val;
}
}
}
max_val
} }
} }

View file

@ -1,14 +1,18 @@
use crate::Float;
use crate::core::spectrum::Spectrum;
use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{TextureEvalContext, TextureMapping2D}; use crate::core::texture::{TextureEvalContext, TextureMapping2D};
use crate::spectra::{SampledSpectrum, SampledWavelengths, SpectrumTrait}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Transform; use crate::utils::{Ptr, Transform};
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FloatBilerpTexture { pub struct FloatBilerpTexture {
mapping: TextureMapping2D, pub mapping: TextureMapping2D,
v00: Float, pub v00: Float,
v01: Float, pub v01: Float,
v10: Float, pub v10: Float,
v11: Float, pub v11: Float,
} }
#[inline(always)] #[inline(always)]
@ -40,22 +44,23 @@ impl FloatBilerpTexture {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpectrumBilerpTexture { pub struct SpectrumBilerpTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub v00: Spectrum, pub v00: Ptr<Spectrum>,
pub v01: Spectrum, pub v01: Ptr<Spectrum>,
pub v10: Spectrum, pub v10: Ptr<Spectrum>,
pub v11: Spectrum, pub v11: Ptr<Spectrum>,
} }
impl SpectrumBilerpTexture { impl SpectrumBilerpTexture {
pub fn new( pub fn new(
mapping: TextureMapping2D, mapping: TextureMapping2D,
v00: Spectrum, v00: Ptr<Spectrum>,
v01: Spectrum, v01: Ptr<Spectrum>,
v10: Spectrum, v10: Ptr<Spectrum>,
v11: Spectrum, v11: Ptr<Spectrum>,
) -> Self { ) -> Self {
Self { Self {
mapping, mapping,
@ -69,16 +74,16 @@ impl SpectrumBilerpTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
bilerp( bilerp(
[c.st[0], c.st[1], c.st[2]], [c.st[0], c.st[1]],
[ [
v00.sample(lambda), self.v00.sample(lambda),
v01.sample(lambda), self.v01.sample(lambda),
v10.sample(lambda), self.v10.sample(lambda),
v11.sample(lambda), self.v11.sample(lambda),
], ],
) )
} }

View file

@ -1,29 +1,105 @@
use crate::core::texture::TextureEvalContext; use crate::Float;
use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D, TextureMapping3D,
TextureMapping3DTrait,
};
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::{ArenaPtr, Ptr, math::square};
// TODO: I have to implement somethign like a TaggedPointer, and change the whole codebase. fn checkerboard(
// Fantastic ctx: &TextureEvalContext,
#[derive(Debug, Clone)] map2d: Ptr<TextureMapping2D>,
map3d: Ptr<TextureMapping3D>,
) -> Float {
let d = |x: Float| -> Float {
let y = x / 2. - (x / 2.).floor() - 0.5;
return x / 2. + y * (1. - 2. * y.abs());
};
let bf = |x: Float, r: Float| -> Float {
if (x.floor() - r) == (x + r).floor() {
return 1. - 2. * (x.floor() as i32 & 1) as Float;
}
(d(x + r) - 2. * d(x) + d(x - r)) / square(r)
};
if !map2d.is_null() {
assert!(map3d.is_null());
let c = map2d.map(&ctx);
let ds = 1.5 * c.dsdx.abs().max(c.dsdy.abs());
let dt = 1.5 * c.dtdx.abs().max(c.dtdy.abs());
// Integrate product of 2D checkerboard function and triangle filter
0.5 - bf(c.st[0], ds) * bf(c.st[1], dt) / 2.
} else {
assert!(!map3d.is_null());
let c = map3d.map(&ctx);
let dx = 1.5 * c.dpdx.x().abs().max(c.dpdy.x().abs());
let dy = 1.5 * c.dpdx.y().abs().max(c.dpdy.y().abs());
let dz = 1.5 * c.dpdx.z().abs().max(c.dpdy.z().abs());
0.5 - bf(c.p.x(), dx) * bf(c.p.y(), dy) * bf(c.p.z(), dz)
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FloatCheckerboardTexture { pub struct FloatCheckerboardTexture {
pub map_2d: TextureMapping2D, pub map2d: Ptr<TextureMapping2D>,
pub map_3d: TextureMapping3D, pub map3d: Ptr<TextureMapping3D>,
pub tex: [FloatTexture; 2], pub tex: [ArenaPtr<GPUFloatTexture>; 2],
} }
impl FloatCheckerboardTexture { impl FloatCheckerboardTexture {
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
todo!() let w = checkerboard(&ctx, self.map2d, self.map3d);
let mut t0 = 0.0;
let mut t1 = 0.0;
if w != 1.0 {
if let Some(tex) = self.tex[0].get() {
t0 = tex.evaluate(ctx);
}
}
if w != 0.0 {
if let Some(tex) = self.tex[1].get() {
t1 = tex.evaluate(ctx);
}
}
(1.0 - w) * t0 + w * t1
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
pub struct SpectrumCheckerboardTexture; #[derive(Clone, Copy, Debug)]
pub struct SpectrumCheckerboardTexture {
pub map2d: Ptr<TextureMapping2D>,
pub map3d: Ptr<TextureMapping3D>,
pub tex: [ArenaPtr<GPUSpectrumTexture>; 2],
}
impl SpectrumCheckerboardTexture { impl SpectrumCheckerboardTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
_ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
todo!() let w = checkerboard(ctx, self.map2d, self.map3d);
let mut t0 = SampledSpectrum::new(0.);
let mut t1 = SampledSpectrum::new(0.);
if w != 1.0 {
if let Some(tex) = self.tex[0].get() {
t0 = tex.evaluate(ctx, lambda);
}
}
if w != 0.0 {
if let Some(tex) = self.tex[1].get() {
t1 = tex.evaluate(ctx, lambda);
}
}
t0 * (1.0 - w) + t1 * w
} }
} }

View file

@ -1,8 +1,10 @@
use crate::Float; use crate::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::TextureEvalContext; use crate::core::texture::TextureEvalContext;
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FloatConstantTexture { pub struct FloatConstantTexture {
pub value: Float, pub value: Float,
} }
@ -17,7 +19,8 @@ impl FloatConstantTexture {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpectrumConstantTexture { pub struct SpectrumConstantTexture {
pub value: Spectrum, pub value: Spectrum,
} }

View file

@ -1,19 +1,80 @@
#[derive(Debug, Clone)] use crate::Float;
pub struct FloatDotsTexture; use crate::core::geometry::{Point2f, VectorLike};
use crate::core::texture::{
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureMapping2D,
};
use crate::spectra::sampled::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::square;
use crate::utils::noise::noise_2d;
fn inside_polka_dot(st: Point2f) -> bool {
let s_cell = (st[0] + 0.5).floor();
let t_cell = (st[1] + 0.5).floor();
if noise_2d(s_cell + 0.5, t_cell + 0.5) > 0. {
let radius = 0.35;
let max_shift = 0.5 + radius;
let s_center = s_cell + max_shift * noise_2d(s_cell + 1.5, t_cell + 2.8);
let t_center = t_cell + max_shift * noise_2d(s_cell + 4.5, t_cell + 9.8);
let dst = st - Point2f::new(s_center, t_center);
if dst.norm_squared() < square(radius) {
return true;
}
}
return false;
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct FloatDotsTexture {
pub mapping: TextureMapping2D,
pub outside_dot: Ptr<GPUFloatTexture>,
pub inside_dot: Ptr<GPUFloatTexture>,
}
impl FloatDotsTexture { impl FloatDotsTexture {
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
todo!() let c = self.mapping.map(ctx);
let target_texture = if inside_polka_dot(c.st) {
self.inside_dot
} else {
self.outside_dot
};
if !target_texture.is_null() {
target_texture.evaluate(ctx)
} else {
0.0
}
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
pub struct SpectrumDotsTexture; #[derive(Clone, Copy, Debug)]
pub struct SpectrumDotsTexture {
pub mapping: TextureMapping2D,
pub outside_dot: Ptr<GPUSpectrumTexture>,
pub inside_dot: Ptr<GPUSpectrumTexture>,
}
impl SpectrumDotsTexture { impl SpectrumDotsTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
_ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
todo!() let c = self.mapping.map(ctx);
let target_texture = if inside_polka_dot(c.st) {
self.inside_dot
} else {
self.outside_dot
};
if !target_texture.is_null() {
target_texture.evaluate(ctx, lambda)
} else {
SampledSpectrum::new(0.0)
}
} }
} }

View file

@ -1,10 +1,12 @@
use crate::Float;
use crate::core::texture::{TextureEvalContext, TextureMapping3D}; use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::utils::noise::fbm;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct FBmTexture { pub struct FBmTexture {
pub mapping: TextureMapping3D, pub mapping: TextureMapping3D,
pub omega: Float, pub omega: Float,
pub octaves: usize, pub octaves: u32,
} }
impl FBmTexture { impl FBmTexture {

View file

@ -1,12 +1,17 @@
use crate::Float; use crate::Float;
use crate::core::color::{RGB, XYZ};
use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D}; use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
use crate::spectra::RGBColorSpace; use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths,
};
/* GPU heavy code, dont know if this will ever work the way Im doing things. /* GPU heavy code, dont know if this will ever work the way Im doing things.
* Leaving it here isolated, for careful handling */ * Leaving it here isolated, for careful handling */
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Copy)]
pub struct GPUSpectrumImageTexture { pub struct GPUSpectrumImageTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub tex_obj: u64, pub tex_obj: u64,
@ -32,8 +37,8 @@ impl GPUSpectrumImageTexture {
{ {
use cuda_std::intrinsics; use cuda_std::intrinsics;
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
let u = c.st.x; let u = c.st.x();
let v = 1.0 - c.st.y; let v = 1.0 - c.st.y();
let d_p_dx = [c.dsdx, c.dtdx]; let d_p_dx = [c.dsdx, c.dtdx];
let d_p_dy = [c.dsdy, c.dtdy]; let d_p_dy = [c.dsdy, c.dtdy];
@ -53,22 +58,20 @@ impl GPUSpectrumImageTexture {
rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero(); rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero();
} }
let color_space = unsafe { &*self.color_space };
match self.spectrum_type { match self.spectrum_type {
SpectrumType::Unbounded => { SpectrumType::Unbounded => {
RGBUnboundedSpectrum::new(color_space, rgb).sample(lambda) RGBUnboundedSpectrum::new(&self.color_space, rgb).sample(lambda)
} }
SpectrumType::Albedo => { SpectrumType::Albedo => {
RGBAlbedoSpectrum::new(color_space, rgb.clamp(0.0, 1.0)).sample(lambda) RGBAlbedoSpectrum::new(&self.color_space, rgb.clamp(0.0, 1.0)).sample(lambda)
} }
_ => RGBIlluminantSpectrum::new(color_space, rgb).sample(lambda), _ => RGBIlluminantSpectrum::new(&self.color_space, rgb).sample(lambda),
} }
} }
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub struct GPUFloatImageTexture { pub struct GPUFloatImageTexture {
pub mapping: TextureMapping2D, pub mapping: TextureMapping2D,
pub tex_obj: u64, pub tex_obj: u64,
@ -86,13 +89,13 @@ impl GPUFloatImageTexture {
{ {
use cuda_std::intrinsics; use cuda_std::intrinsics;
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
let u = c.st.x; let u = c.st.x();
let v = 1.0 - c.st.y; let v = 1.0 - c.st.y();
let d_p_dx = [c.dsdx, c.dtdx]; let d_p_dx = [c.dsdx, c.dtdx];
let d_p_dy = [c.dsdy, c.dtdy]; let d_p_dy = [c.dsdy, c.dtdy];
let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) }; let val: Float = unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
if invert { if self.invert {
return (1. - v).max(0.); return (1. - v).max(0.);
} else { } else {
return v; return v;

View file

@ -1,56 +1,67 @@
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::spectra::{RGBAlbedoSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::noise::fbm;
use crate::utils::splines::evaluate_cubic_bezier;
use crate::Float; use crate::Float;
use crate::core::color::RGB;
use crate::core::geometry::{Point3f, Vector3f};
use crate::core::spectrum::SpectrumTrait;
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::spectra::{RGBAlbedoSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths};
use crate::utils::math::clamp;
use crate::utils::noise::fbm;
use crate::utils::ptr::Ptr;
use crate::utils::splines::evaluate_cubic_bezier;
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct MarbleTexture { pub struct MarbleTexture {
pub mapping: TextureMapping3D, pub mapping: TextureMapping3D,
pub octaves: usize, pub octaves: u32,
pub omega: Float, pub omega: Float,
pub scale: Float, pub scale: Float,
pub variation: Float, pub variation: Float,
// TODO: DO not forget to pass StandardColorSpace here!!
pub colorspace: Ptr<RGBColorSpace>,
} }
unsafe impl Send for MarbleTexture {}
unsafe impl Sync for MarbleTexture {}
impl MarbleTexture { impl MarbleTexture {
pub fn evaluate( pub fn evaluate(
&self, &self,
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
_lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let mut c = self.mapping.map(ctx); let mut c = self.mapping.map(ctx);
c.p *= self.scale; c.p = Point3f::from(Vector3f::from(c.p) * self.scale);
let marble = c.p.y() + self.variation * fbm(c.p, self.scale, c.dpdy, omega, self.octaves); let marble = c.p.y()
const COLORS: [RGB; 9] = [ + self.variation
RGB::new(0.58, 0.58, 0.6), * fbm(
RGB::new(0.58, 0.58, 0.6), c.p,
RGB::new(0.58, 0.58, 0.6), self.scale * c.dpdx,
RGB::new(0.5, 0.5, 0.5), self.scale * c.dpdy,
RGB::new(0.6, 0.59, 0.58), self.omega,
RGB::new(0.58, 0.58, 0.6), self.octaves,
RGB::new(0.58, 0.58, 0.6), );
RGB::new(0.2, 0.2, 0.33), let t = 0.5 + 0.5 * marble.sin();
RGB::new(0.58, 0.58, 0.6), let colors: [Point3f; 9] = [
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.5, 0.5, 0.5),
Point3f::new(0.6, 0.59, 0.58),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.58, 0.58, 0.6),
Point3f::new(0.2, 0.2, 0.33),
Point3f::new(0.58, 0.58, 0.6),
]; ];
const N_SEG: i32 = 6; // (9 - 3) const N_SEG: i32 = 6; // (9 - 3)
let t_clamped = t.clamp(0.0, 1.0); let t_clamped = clamp(t, 0.0, 1.0);
let first = ((t_clamped * N_SEG as Float).floor() as i32).clamp(0, N_SEG - 1); let first = ((t_clamped * N_SEG as Float).floor() as i32).clamp(0, N_SEG - 1);
let t_segment = t_clamped * N_SEG as Float - first as Float; let t_segment = t_clamped * N_SEG as Float - first as Float;
let first_idx = first as usize; let first_idx = first as usize;
let rgb = evaluate_cubic_bezier(&COLORS[first_idx..first_idx + 4], t_segment) * 1.5; let (rgb_vec, _) = evaluate_cubic_bezier(&colors[first_idx..first_idx + 4], t_segment);
let rgb = RGB::new(rgb_vec.x() * 1.5, rgb_vec.y() * 1.5, rgb_vec.z() * 1.5);
let color_space = { RGBAlbedoSpectrum::new(&*self.colorspace, rgb).sample(lambda)
#[cfg(target_os = "cuda")]
{
unsafe { &*RGBColorSpace_sRGB }
}
#[cfg(not(target_os = "cuda"))]
{
RGBColorSpace::srgb()
}
};
RGBAlbedoSpectrum::new(color_space, rgb).sample(lambda)
} }
} }

View file

@ -1,33 +1,133 @@
use crate::core::geometry::Vector3f; use crate::Float;
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use crate::core::geometry::{Vector3f, VectorLike};
use crate::utils::Ptr; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::ArenaPtr;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct GPUFloatMixTexture { pub struct GPUFloatMixTexture {
pub tex1: Ptr<GPUFloatTexture>, pub tex1: ArenaPtr<GPUFloatTexture>,
pub tex2: Ptr<GPUFloatTexture>, pub tex2: ArenaPtr<GPUFloatTexture>,
pub amount: ArenaPtr<GPUFloatTexture>,
}
impl GPUFloatMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else {
0.0
};
let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else {
0.0
};
(1.0 - amt) * t1 + amt * t2
}
} }
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct GPUFloatDirectionMixTexture { pub struct GPUFloatDirectionMixTexture {
pub tex1: Ptr<GPUFloatTexture>, pub tex1: ArenaPtr<GPUFloatTexture>,
pub tex2: Ptr<GPUFloatTexture>, pub tex2: ArenaPtr<GPUFloatTexture>,
pub dir: Vector3f, pub dir: Vector3f,
} }
impl GPUFloatDirectionMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.dir.abs_dot(ctx.n.into());
let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else {
0.0
};
let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0)
} else {
0.0
};
(1.0 - amt) * t1 + amt * t2
}
}
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct GPUSpectrumMixTexture { pub struct GPUSpectrumMixTexture {
pub tex1: Ptr<GPUSpectrumTexture>, pub tex1: ArenaPtr<GPUSpectrumTexture>,
pub tex2: Ptr<GPUSpectrumTexture>, pub tex2: ArenaPtr<GPUSpectrumTexture>,
pub amount: ArenaPtr<GPUFloatTexture>,
}
impl GPUSpectrumMixTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 {
self.tex1
.get()
.map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
let t2 = if amt != 0.0 {
self.tex2
.get()
.map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
(1.0 - amt) * t1 + amt * t2
}
} }
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct GPUSpectrumDirectionMixTexture { pub struct GPUSpectrumDirectionMixTexture {
pub tex1: Ptr<GPUSpectrumTexture>, pub tex1: ArenaPtr<GPUSpectrumTexture>,
pub tex2: Ptr<GPUSpectrumTexture>, pub tex2: ArenaPtr<GPUSpectrumTexture>,
pub dir: Vector3f, pub dir: Vector3f,
} }
impl GPUSpectrumDirectionMixTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
let amt = self.dir.abs_dot(ctx.n.into());
let t1 = if amt != 1.0 {
self.tex1
.get()
.map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
let t2 = if amt != 0.0 {
self.tex2
.get()
.map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.))
} else {
SampledSpectrum::new(0.)
};
(1.0 - amt) * t1 + amt * t2
}
}

View file

@ -1,29 +1,32 @@
use crate::Float; use crate::Float;
use crate::core::color::RGB;
use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
use crate::core::texture::{SpectrumType, TextureEvalContext}; use crate::core::texture::{SpectrumType, TextureEvalContext};
use crate::spectra::{ use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, SampledWavelengths, StandardColorSpaces,
}; };
use crate::utils::ptr::{Ptr, Slice};
/* GPU heavy code, have to see how to best approach this #[repr(C)]
*/ #[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct GPUFloatPtexTexture { pub struct GPUFloatPtexTexture {
pub face_values: Vec<Float>, pub face_values: *const Float,
} }
impl GPUFloatPtexTexture { impl GPUFloatPtexTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
self.face_values[ctx.face_index] unsafe { *self.face_values.add(ctx.face_index as usize) }
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct GPUSpectrumPtexTexture { pub struct GPUSpectrumPtexTexture {
pub face_values: *const RGB, pub face_values: Slice<RGB>,
pub n_faces: usize, pub n_faces: u32,
pub spectrum_type: SpectrumType, pub spectrum_type: SpectrumType,
pub colorspaces: StandardColorSpaces,
} }
impl GPUSpectrumPtexTexture { impl GPUSpectrumPtexTexture {
@ -33,23 +36,16 @@ impl GPUSpectrumPtexTexture {
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let index = ctx.face_index.clamp(0, self.n_faces.saturating_sub(1)); let index = ctx.face_index.clamp(0, self.n_faces.saturating_sub(1));
let rgb = unsafe { *self.face_values.add(index) }; let rgb = self.face_values[index as usize];
let s_rgb = { let s_rgb = self.colorspaces.srgb;
#[cfg(feature = "cuda")]
unsafe {
&*RGBColorSpace_sRGB
}
#[cfg(not(feature = "cuda"))]
RGBColorSpace::srgb()
};
match self.spectrum_type { match self.spectrum_type {
SpectrumType::Unbounded => RGBUnboundedSpectrum::new(s_rgb, rgb).sample(lambda), SpectrumType::Unbounded => RGBUnboundedSpectrum::new(&s_rgb, rgb).sample(lambda),
SpectrumType::Albedo => { SpectrumType::Albedo => {
let clamped_rgb = rgb.clamp(0.0, 1.0); let clamped_rgb = rgb.clamp(0.0, 1.0);
RGBAlbedoSpectrum::new(s_rgb, clamped_rgb).sample(lambda) RGBAlbedoSpectrum::new(&s_rgb, clamped_rgb).sample(lambda)
} }
SpectrumType::Illuminant => RGBIlluminantSpectrum::new(s_rgb, rgb).sample(lambda), SpectrumType::Illuminant => RGBIlluminantSpectrum::new(&s_rgb, rgb).sample(lambda),
} }
} }
} }

View file

@ -1,16 +1,44 @@
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use crate::Float;
use crate::utils::Ptr; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::ArenaPtr;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GPUFloatScaledTexture { pub struct GPUFloatScaledTexture {
tex: Ptr<GPUFloatTexture>, pub tex: ArenaPtr<GPUFloatTexture>,
scale: Ptr<GPUFloatTexture>, pub scale: ArenaPtr<GPUFloatTexture>,
}
impl GPUFloatScaledTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let sc = self.scale.get().map(|t| t.evaluate(&ctx)).unwrap();
if sc == 0. {
return 0.;
}
self.tex.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) * sc
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GPUSpectrumScaledTexture { pub struct GPUSpectrumScaledTexture {
tex: Ptr<GPUSpectrumTexture>, pub tex: ArenaPtr<GPUSpectrumTexture>,
scale: Ptr<GPUFloatTexture>, pub scale: ArenaPtr<GPUFloatTexture>,
}
impl GPUSpectrumScaledTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
let sc = self.scale.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.);
self.tex
.get()
.map(|t| t.evaluate(&ctx, &lambda))
.unwrap_or(SampledSpectrum::new(0.))
* sc
}
} }

View file

@ -1,7 +1,10 @@
use crate::Float;
use crate::core::geometry::{Point3f, Vector3f};
use crate::core::texture::{TextureEvalContext, TextureMapping3D}; use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::utils::noise::fbm; use crate::utils::noise::fbm;
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct WindyTexture { pub struct WindyTexture {
pub mapping: TextureMapping3D, pub mapping: TextureMapping3D,
} }
@ -9,7 +12,13 @@ pub struct WindyTexture {
impl WindyTexture { impl WindyTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
let wind_strength = fbm(0.1 * c.p, 0.1 * c.dpdx, 0.1 * c.dpdy, 0.5, 3); let wind_strength = fbm(
Point3f::from(0.1 * Vector3f::from(c.p)),
0.1 * c.dpdx,
0.1 * c.dpdy,
0.5,
3,
);
let wave_height = fbm(c.p, c.dpdx, c.dpdy, 0.5, 6); let wave_height = fbm(c.p, c.dpdx, c.dpdy, 0.5, 6);
wind_strength.abs() * wave_height wind_strength.abs() * wave_height
} }

View file

@ -1,16 +1,18 @@
use crate::Float;
use crate::core::texture::{TextureEvalContext, TextureMapping3D}; use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::utils::noise::turbulence; use crate::utils::noise::turbulence;
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct WrinkledTexture { pub struct WrinkledTexture {
pub mapping: TextureMapping3D, pub mapping: TextureMapping3D,
pub octaves: usize, pub octaves: u32,
pub omega: Float, pub omega: Float,
} }
impl WrinkledTexture { impl WrinkledTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let c = self.mapping.map(ctx); let c = self.mapping.map(ctx);
turbulence(c.p, c.dpdx, c.dpdy, omega, octaves) turbulence(c.p, c.dpdx, c.dpdy, self.omega, self.octaves)
} }
} }

View file

@ -10,53 +10,65 @@ use crate::core::geometry::{
Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i, Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i,
}; };
// pub trait Interpolatable: pub trait Interpolatable:
// Copy + Default + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self> Copy + Default + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self>
// { {
// } }
//
// impl<T> Interpolatable for T where impl<T> Interpolatable for T where
// T: Copy + Default + Add<Output = T> + Sub<Output = T> + Mul<Float, Output = T> T: Copy + Default + Add<Output = T> + Sub<Output = T> + Mul<Float, Output = T>
// { {
// } }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Array2D<T> { pub struct Array2D<T> {
pub values: *mut T, pub values: *mut T,
pub extent: Bounds2i, pub extent: Bounds2i,
pub x_stride: i32,
} }
unsafe impl<T: Send> Send for Array2D<T> {}
unsafe impl<T: Sync> Sync for Array2D<T> {}
impl<T> Array2D<T> { impl<T> Array2D<T> {
#[inline] #[inline]
pub fn x_size(&self) -> usize { pub fn x_size(&self) -> u32 {
(self.extent.p_max.x() - self.extent.p_min.x()) as usize (self.extent.p_max.x() - self.extent.p_min.x()) as u32
} }
#[inline] #[inline]
pub fn y_size(&self) -> usize { pub fn y_size(&self) -> u32 {
(self.extent.p_max.y() - self.extent.p_min.y()) as usize (self.extent.p_max.y() - self.extent.p_min.y()) as u32
} }
#[inline] #[inline]
pub fn size(&self) -> usize { pub fn size(&self) -> u32 {
self.extent.area() as usize self.extent.area() as u32
}
#[inline(always)]
fn offset(&self, p: Point2i) -> isize {
let ox = p.x() - self.extent.p_min.x();
let oy = p.y() - self.extent.p_min.y();
(ox + oy * self.x_stride) as isize
} }
#[inline] #[inline]
pub fn index(&self, x: i32, y: i32) -> usize { pub fn index(&self, x: i32, y: i32) -> u32 {
let nx = x - self.extent.p_min.x; let nx = x - self.extent.p_min.x();
let ny = y - self.extent.p_min.y; let ny = y - self.extent.p_min.y();
(nx + self.x_size() * ny) as usize nx as u32 + self.x_size() * ny as u32
} }
#[inline] #[inline(always)]
pub unsafe fn get(&self, x: i32, y: i32) -> &T { pub fn get(&self, p: Point2i) -> &T {
unsafe { &*self.values.add(self.index(x, y)) } unsafe { &*self.values.offset(self.offset(p)) }
} }
#[inline] #[inline(always)]
pub unsafe fn get_mut(&mut self, x: i32, y: i32) -> &mut T { pub fn get_mut(&mut self, p: Point2i) -> &mut T {
unsafe { &mut *self.values.add(self.index(x, y)) } unsafe { &mut *self.values.offset(self.offset(p)) }
} }
#[inline] #[inline]
@ -66,121 +78,50 @@ impl<T> Array2D<T> {
#[inline] #[inline]
pub fn get_linear_mut(&mut self, index: usize) -> &mut T { pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
// SAFETY: Caller must ensure index < size()
unsafe { &mut *self.values.add(index) } unsafe { &mut *self.values.add(index) }
} }
pub fn as_slice(&self) -> &[T] { pub fn as_slice(&self) -> &[T] {
unsafe { core::slice::from_raw_parts(self.values, self.size()) } unsafe { core::slice::from_raw_parts(self.values, self.size() as usize) }
} }
pub fn as_mut_slice(&mut self) -> &mut [T] { pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.values, self.size()) } unsafe { core::slice::from_raw_parts_mut(self.values, self.size() as usize) }
} }
} }
#[cfg(not(target_os = "cuda"))]
impl<T: Clone> Clone for Array2D<T> {
fn clone(&self) -> Self {
let n = self.area();
let mut v = Vec::with_capacity(n);
unsafe {
for i in 0..n {
v.push((*self.values.add(i)).clone());
}
}
let values = v.as_mut_ptr();
std::mem::forget(v);
Self {
extent: self.extent,
values,
}
}
}
#[cfg(target_os = "cuda")]
impl<T> Clone for Array2D<T> {
fn clone(&self) -> Self {
*self
}
}
#[cfg(target_os = "cuda")]
impl<T> Copy for Array2D<T> {}
#[cfg(not(target_os = "cuda"))]
impl<T: Default + Clone> Array2D<T> {
pub fn new(extent: Bounds2i) -> Self {
let n = extent.area() as usize;
let mut v = vec![T::default(); n];
let values = v.as_mut_ptr();
std::mem::forget(v);
Self { extent, values }
}
pub fn new_with_dims(nx: usize, ny: usize) -> Self {
let extent = Bounds2i::new(Point2i::new(0, 0), Point2i::new(nx, ny));
let n = extent.area() as usize;
let mut v = vec![T::default(); n];
let values = v.as_mut_ptr();
std::mem::forget(v);
Self { extent, values }
}
pub fn new_from_bounds(extent: Bounds2i, default_val: T) -> Self {
let n = extent.area() as usize;
let mut v = vec![def; n];
let values = v.as_mut_ptr();
std::mem::forget(v);
Self { extent, values }
}
pub fn new_filled(width: usize, height: usize, value: T) -> Self {
let extent = Bounds2i::from_points(
Point2i::new(0, 0),
Point2i::new(width as i32, height as i32),
);
Self::new_from_bounds(extent, value)
}
}
#[cfg(not(feature = "cuda"))]
impl<T> Index<Point2i> for Array2D<T> { impl<T> Index<Point2i> for Array2D<T> {
type Output = T; type Output = T;
#[inline(always)]
fn index(&self, mut p: Point2i) -> &Self::Output { fn index(&self, p: Point2i) -> &Self::Output {
unsafe { self.get(pos.0, pos.1) } self.get(p)
} }
} }
#[cfg(not(feature = "cuda"))]
impl<T> IndexMut<Point2i> for Array2D<T> { impl<T> IndexMut<Point2i> for Array2D<T> {
fn index_mut(&mut self, mut p: Point2i) -> &mut Self::Output { fn index_mut(&mut self, p: Point2i) -> &mut Self::Output {
unsafe { self.get_mut(pos.0, pos.1) } self.get_mut(p)
} }
} }
#[cfg(not(feature = "cuda"))]
impl<T> Index<(i32, i32)> for Array2D<T> { impl<T> Index<(i32, i32)> for Array2D<T> {
type Output = T; type Output = T;
fn index(&self, pos: (i32, i32)) -> &Self::Output { fn index(&self, (x, y): (i32, i32)) -> &Self::Output {
unsafe { self.get(pos.0, pos.1) } &self[Point2i::new(x, y)]
} }
} }
#[cfg(not(feature = "cuda"))]
impl<T> IndexMut<(i32, i32)> for Array2D<T> { impl<T> IndexMut<(i32, i32)> for Array2D<T> {
fn index_mut(&mut self, pos: (i32, i32)) -> &mut Self::Output { fn index_mut(&mut self, (x, y): (i32, i32)) -> &mut Self::Output {
unsafe { self.get_mut(pos.0, pos.1) } &mut self[Point2i::new(x, y)]
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SampledGrid<T> { pub struct SampledGrid<T> {
pub values: *const T, pub values: *const T,
pub values_len: u32,
pub nx: i32, pub nx: i32,
pub ny: i32, pub ny: i32,
pub nz: i32, pub nz: i32,
@ -195,6 +136,7 @@ impl<T> SampledGrid<T> {
assert_eq!(slice.len(), (nx * ny * nz) as usize); assert_eq!(slice.len(), (nx * ny * nz) as usize);
Self { Self {
values: slice.as_ptr(), values: slice.as_ptr(),
values_len: (nx * ny * nz) as u32,
nx, nx,
ny, ny,
nz, nz,
@ -204,6 +146,7 @@ impl<T> SampledGrid<T> {
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
values: core::ptr::null(), values: core::ptr::null(),
values_len: 0,
nx: 0, nx: 0,
ny: 0, ny: 0,
nz: 0, nz: 0,
@ -214,8 +157,8 @@ impl<T> SampledGrid<T> {
!self.values.is_null() && self.nx > 0 && self.ny > 0 && self.nz > 0 !self.values.is_null() && self.nx > 0 && self.ny > 0 && self.nz > 0
} }
pub fn bytes_allocated(&self) -> usize { pub fn bytes_allocated(&self) -> u32 {
self.values.len() * std::mem::size_of::<T>() self.values_len * std::mem::size_of::<T>() as u32
} }
pub fn x_size(&self) -> i32 { pub fn x_size(&self) -> i32 {
@ -239,7 +182,7 @@ impl<T> SampledGrid<T> {
return U::default(); return U::default();
} }
let sample_bounds = Bounds3i::new( let sample_bounds = Bounds3i::from_points(
Point3i::new(0, 0, 0), Point3i::new(0, 0, 0),
Point3i::new(self.nx, self.ny, self.nz), Point3i::new(self.nx, self.ny, self.nz),
); );

View file

@ -1,9 +1,5 @@
use image_rs::{ImageError as IError, error};
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error;
use crate::images::PixelFormat;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum LlsError { pub enum LlsError {

View file

@ -1,10 +1,12 @@
use super::error::{InversionError, LlsError}; use super::error::{InversionError, LlsError};
use crate::core::geometry::{Lerp, Point, Point2f, Point2i, Vector, Vector3f, VectorLike}; use crate::core::color::{RGB, XYZ};
use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4}; use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4};
use crate::spectra::color::{RGB, XYZ};
use crate::utils::hash::{hash_buffer, mix_bits}; use crate::utils::hash::{hash_buffer, mix_bits};
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
use crate::utils::Ptr;
use half::f16;
use num_traits::{Float as NumFloat, Num, One, Signed, Zero}; use num_traits::{Float as NumFloat, Num, One, Signed, Zero};
use std::error::Error; use std::error::Error;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
@ -72,15 +74,15 @@ pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
result result
} }
#[inline] pub fn difference_of_products<T>(a: Float, b: T, c: Float, d: T) -> T
pub fn difference_of_products<T>(a: T, b: T, c: T, d: T) -> T
where where
T: Mul<Output = T> + Add<Output = T> + Neg<Output = T> + Copy, T: Copy + Neg<Output = T> + Mul<Float, Output = T> + Add<Output = T>,
T: MulAdd<Float, T, Output = T>,
{ {
let cd = c * d; let cd = d * c;
let difference_of_products = fma(a, b, -cd); let diff = b.mul_add(a, -cd);
let error = fma(-c, d, cd); let error = d.mul_add(-c, cd);
difference_of_products + error diff + error
} }
#[inline] #[inline]
@ -147,8 +149,7 @@ pub fn fast_exp(x: Float) -> Float {
let fxp = xp.floor(); let fxp = xp.floor();
let f = xp - fxp; let f = xp - fxp;
let i = fxp as i32; let i = fxp as i32;
let two_to_f = evaluate_polynomial(f, &[1., 0.695556856, 0.226173572, 0.0781455737]) let two_to_f = evaluate_polynomial(f, &[1., 0.695556856, 0.226173572, 0.0781455737]);
.expect("Could not evaluate polynomial");
let exponent = exponent(two_to_f) + i; let exponent = exponent(two_to_f) + i;
if exponent < -126 { if exponent < -126 {
return 0.; return 0.;
@ -341,7 +342,7 @@ pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f {
*uv *uv
} }
pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(usize, [Float; 4])> { pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4])> {
if nodes.len() < 4 { if nodes.len() < 4 {
return None; return None;
} }
@ -398,7 +399,7 @@ pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(usize, [Float;
weights[3] = 0.0; weights[3] = 0.0;
} }
Some((offset, weights)) Some((offset as u32, weights))
} }
pub fn equal_area_sphere_to_square(d: Vector3f) -> Point2f { pub fn equal_area_sphere_to_square(d: Vector3f) -> Point2f {
@ -416,8 +417,7 @@ pub fn equal_area_sphere_to_square(d: Vector3f) -> Point2f {
let t5 = 0.881770664775316294736387951347e-1; let t5 = 0.881770664775316294736387951347e-1;
let t6 = 0.419038818029165735901852432784e-1; let t6 = 0.419038818029165735901852432784e-1;
let t7 = -0.251390972343483509333252996350e-1; let t7 = -0.251390972343483509333252996350e-1;
let mut phi = evaluate_polynomial(b, &[t1, t2, t3, t4, t5, t6, t7]) let mut phi = evaluate_polynomial(b, &[t1, t2, t3, t4, t5, t6, t7]);
.expect("Could not evaluate polynomial");
if x < y { if x < y {
phi = 1. - phi; phi = 1. - phi;
@ -713,8 +713,8 @@ const PRIMES: [i32; PRIME_TABLE_SIZE] = [
]; ];
#[inline] #[inline]
pub fn radical_inverse(base_index: usize, mut a: u64) -> Float { pub fn radical_inverse(base_index: u32, mut a: u64) -> Float {
let base = PRIMES[base_index] as u64; let base = PRIMES[base_index as usize] as u64;
let limit = (u64::MAX / base).saturating_sub(base); let limit = (u64::MAX / base).saturating_sub(base);
@ -748,45 +748,15 @@ pub fn inverse_radical_inverse(mut inverse: u64, base: u64, n_digits: u64) -> u6
} }
// Digit scrambling // Digit scrambling
#[derive(Default, Debug, Clone)] #[repr(C)]
#[derive(Default, Debug, Copy, Clone)]
pub struct DigitPermutation { pub struct DigitPermutation {
base: usize, pub base: u32,
n_digits: usize, pub n_digits: u32,
permutations: Vec<u16>, pub permutations: Ptr<u16>,
} }
impl DigitPermutation { impl DigitPermutation {
pub fn new(base: usize, seed: u64) -> Self {
let mut n_digits = 0;
let inv_base = 1. / base as Float;
let mut inv_base_m = 1.;
while 1.0 - ((base as Float - 1.0) * inv_base_m) < 1.0 {
n_digits += 1;
inv_base_m *= inv_base;
}
let mut permutations = vec![0u16; n_digits * base];
for digit_index in 0..n_digits {
let hash_input = [base as u64, digit_index as u64, seed];
let dseed = hash_buffer(&hash_input, 0);
for digit_value in 0..base {
let index = digit_index * base + digit_value;
permutations[index] =
permutation_element(digit_value as u32, base as u32, dseed as u32) as u16;
}
}
Self {
base,
n_digits,
permutations,
}
}
#[inline(always)] #[inline(always)]
pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 { pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 {
let idx = (digit_index * self.base as i32 + digit_value) as usize; let idx = (digit_index * self.base as i32 + digit_value) as usize;
@ -794,15 +764,8 @@ impl DigitPermutation {
} }
} }
pub fn compute_radical_inverse_permutations(seed: u64) -> Vec<DigitPermutation> { pub fn scrambled_radical_inverse(base_index: u32, mut a: u64, perm: &DigitPermutation) -> Float {
PRIMES let base = PRIMES[base_index as usize] as u64;
.par_iter()
.map(|&base| DigitPermutation::new(base as usize, seed))
.collect()
}
pub fn scrambled_radical_inverse(base_index: usize, mut a: u64, perm: &DigitPermutation) -> Float {
let base = PRIMES[base_index] as u64;
let limit = (u64::MAX / base).saturating_sub(base); let limit = (u64::MAX / base).saturating_sub(base);
@ -827,8 +790,8 @@ pub fn scrambled_radical_inverse(base_index: usize, mut a: u64, perm: &DigitPerm
(inv_base_m * reversed_digits as Float).min(ONE_MINUS_EPSILON) (inv_base_m * reversed_digits as Float).min(ONE_MINUS_EPSILON)
} }
pub fn owen_scrambled_radical_inverse(base_index: usize, mut a: u64, hash: u32) -> Float { pub fn owen_scrambled_radical_inverse(base_index: u32, mut a: u64, hash: u32) -> Float {
let base = PRIMES[base_index] as u64; let base = PRIMES[base_index as usize] as u64;
let limit = (u64::MAX / base).saturating_sub(base); let limit = (u64::MAX / base).saturating_sub(base);
let inv_base = 1.0 / (base as Float); let inv_base = 1.0 / (base as Float);
@ -1029,10 +992,10 @@ impl<F: Fn(u32) -> u32> Scrambler for F {
} }
} }
const N_SOBOL_DIMENSIONS: usize = 1024; const N_SOBOL_DIMENSIONS: u32 = 1024;
const SOBOL_MATRIX_SIZE: usize = 52; const SOBOL_MATRIX_SIZE: u32 = 52;
#[inline] #[inline]
pub fn sobol_sample<S: Scrambler>(mut a: u64, dimension: usize, randomizer: S) -> Float { pub fn sobol_sample<S: Scrambler>(mut a: u64, dimension: u32, randomizer: S) -> Float {
debug_assert!( debug_assert!(
dimension < N_SOBOL_DIMENSIONS, dimension < N_SOBOL_DIMENSIONS,
"Sobol dimension out of bounds" "Sobol dimension out of bounds"
@ -1045,7 +1008,7 @@ pub fn sobol_sample<S: Scrambler>(mut a: u64, dimension: usize, randomizer: S) -
while a != 0 { while a != 0 {
if (a & 1) != 0 { if (a & 1) != 0 {
v ^= SOBOL_MATRICES_32[i]; v ^= SOBOL_MATRICES_32[i as usize];
} }
a >>= 1; a >>= 1;
i += 1; i += 1;
@ -1571,3 +1534,53 @@ mod tests {
assert_eq!(m.determinant(), 1.0); assert_eq!(m.determinant(), 1.0);
} }
} }
#[inline(always)]
pub fn f16_to_f32(bits: u16) -> f32 {
#[cfg(target_os = "cuda")]
{
// Use hardware intrinsic on CUDA
// Cast bits to cuda_std::f16, then cast to f32
let half_val = unsafe { core::mem::transmute::<u16, cuda_std::f16>(bits) };
half_val.to_f32()
}
#[cfg(target_arch = "spirv")]
{
// Use shared logic or spirv-std intrinsics if available.
// Sadly, f16 support in rust-gpu is still maturing.
// A manual bit-conversion function is often safest here.
f16_to_f32_software(bits)
}
#[cfg(not(any(target_os = "cuda", target_arch = "spirv")))]
{
f16::from_bits(bits).to_f32()
}
}
fn f16_to_f32_software(h: u16) -> f32 {
let sign = ((h >> 15) & 1) as u32;
let exp = ((h >> 10) & 0x1F) as u32;
let mant = (h & 0x3FF) as u32;
let out_bits = if exp == 0 {
if mant == 0 {
sign << 31
} else {
let mut m = mant;
let mut e = 0;
while (m & 0x400) == 0 {
m <<= 1;
e += 1;
}
(sign << 31) | ((112 - e) << 23) | ((m & 0x3FF) << 13)
}
} else if exp == 0x1F {
(sign << 31) | (0xFF << 23) | (mant << 13)
} else {
(sign << 31) | ((exp + 112) << 23) | (mant << 13)
};
f32::from_bits(out_bits)
}

View file

@ -1,162 +1,34 @@
use crate::Float;
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f}; use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
use crate::core::pbrt::Float; use crate::utils::Transform;
use crate::utils::ptr::Ptr;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::transform::TransformGeneric;
use std::sync::Arc;
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct TriangleMesh { pub struct TriangleMesh {
pub n_triangles: usize, pub n_triangles: u32,
pub n_vertices: usize, pub n_vertices: u32,
pub vertex_indices: Arc<Vec<usize>>, pub vertex_indices: Ptr<u32>,
pub p: Arc<Vec<Point3f>>, pub p: Ptr<Point3f>,
pub n: Option<Arc<Vec<Normal3f>>>, pub n: Ptr<Normal3f>,
pub s: Option<Arc<Vec<Vector3f>>>, pub s: Ptr<Vector3f>,
pub uv: Option<Arc<Vec<Point2f>>>, pub uv: Ptr<Point2f>,
pub face_indices: Option<Arc<Vec<usize>>>, pub face_indices: Ptr<u32>,
pub reverse_orientation: bool, pub reverse_orientation: bool,
pub transform_swaps_handedness: bool, pub transform_swaps_handedness: bool,
} }
impl TriangleMesh { #[repr(C)]
#[allow(clippy::too_many_arguments)] #[derive(Debug, Clone, Copy)]
pub fn new(
render_from_object: &TransformGeneric<Float>,
reverse_orientation: bool,
indices: Vec<usize>,
mut p: Vec<Point3f>,
mut s: Vec<Vector3f>,
mut n: Vec<Normal3f>,
uv: Vec<Point2f>,
face_indices: Vec<usize>,
) -> Self {
let n_triangles = indices.len() / 3;
let n_vertices = p.len();
for pt in p.iter_mut() {
*pt = render_from_object.apply_to_point(*pt);
}
let transform_swaps_handedness = render_from_object.swaps_handedness();
let uv = if !uv.is_empty() {
assert_eq!(n_vertices, uv.len());
Some(uv)
} else {
None
};
let n = if !n.is_empty() {
assert_eq!(n_vertices, n.len());
for nn in n.iter_mut() {
*nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
*nn = -*nn;
}
}
Some(n)
} else {
None
};
let s = if !s.is_empty() {
assert_eq!(n_vertices, s.len());
for ss in s.iter_mut() {
*ss = render_from_object.apply_to_vector(*ss);
}
Some(s)
} else {
None
};
let face_indices = if !face_indices.is_empty() {
assert_eq!(n_triangles, face_indices.len());
Some(face_indices)
} else {
None
};
assert!(p.len() <= i32::MAX as usize);
assert!(indices.len() <= i32::MAX as usize);
Self {
n_triangles,
n_vertices,
vertex_indices: Arc::new(indices),
p: Arc::new(p),
n: n.map(Arc::new),
s: s.map(Arc::new),
uv: uv.map(Arc::new),
face_indices: face_indices.map(Arc::new),
reverse_orientation,
transform_swaps_handedness,
}
}
}
#[derive(Debug, Clone)]
pub struct BilinearPatchMesh { pub struct BilinearPatchMesh {
pub reverse_orientation: bool, pub reverse_orientation: bool,
pub transform_swaps_handedness: bool, pub transform_swaps_handedness: bool,
pub n_patches: usize, pub n_patches: u32,
pub n_vertices: usize, pub n_vertices: u32,
pub vertex_indices: Arc<Vec<usize>>, pub vertex_indices: Ptr<u32>,
pub p: Arc<Vec<Point3f>>, pub p: Ptr<Point3f>,
pub n: Option<Arc<Vec<Normal3f>>>, pub n: Ptr<Normal3f>,
pub uv: Option<Arc<Vec<Point2f>>>, pub uv: Ptr<Point2f>,
pub image_distribution: Option<PiecewiseConstant2D>, pub image_distribution: Ptr<PiecewiseConstant2D>,
}
impl BilinearPatchMesh {
pub fn new(
render_from_object: &TransformGeneric<Float>,
reverse_orientation: bool,
indices: Vec<usize>,
mut p: Vec<Point3f>,
mut n: Vec<Normal3f>,
uv: Vec<Point2f>,
image_distribution: PiecewiseConstant2D,
) -> Self {
let n_patches = indices.len() / 3;
let n_vertices = p.len();
for pt in p.iter_mut() {
*pt = render_from_object.apply_to_point(*pt);
}
let transform_swaps_handedness = render_from_object.swaps_handedness();
let uv = if !uv.is_empty() {
assert_eq!(n_vertices, uv.len());
Some(uv)
} else {
None
};
let n = if !n.is_empty() {
assert_eq!(n_vertices, n.len());
for nn in n.iter_mut() {
*nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
*nn = -*nn;
}
}
Some(n)
} else {
None
};
assert!(p.len() <= i32::MAX as usize);
assert!(indices.len() <= i32::MAX as usize);
Self {
n_patches,
n_vertices,
vertex_indices: Arc::new(indices),
p: Arc::new(p),
n: n.map(Arc::new),
uv: uv.map(Arc::new),
reverse_orientation,
transform_swaps_handedness,
image_distribution: Some(image_distribution),
}
}
} }

View file

@ -1,4 +1,4 @@
use std::sync::atomic::{AtomicU64, Ordering}; use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
pub mod containers; pub mod containers;
pub mod error; pub mod error;
@ -15,7 +15,7 @@ pub mod sobol;
pub mod splines; pub mod splines;
pub mod transform; pub mod transform;
pub use ptr::Ptr; pub use ptr::{ArenaPtr, Ptr};
pub use transform::{AnimatedTransform, Transform, TransformGeneric}; pub use transform::{AnimatedTransform, Transform, TransformGeneric};
#[inline] #[inline]
@ -33,49 +33,37 @@ where
i i
} }
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct AtomicFloat { pub struct AtomicFloat {
value: f64, bits: AtomicU32,
} }
impl AtomicFloat { impl AtomicFloat {
pub fn new(value: f64) -> Self { pub fn new(val: f32) -> Self {
Self { value } Self {
bits: AtomicU32::new(val.to_bits()),
}
} }
pub fn load(&self) -> f64 { pub fn get(&self) -> f32 {
#[cfg(not(target_os = "cuda"))] f32::from_bits(self.bits.load(Ordering::Relaxed))
{
use core::sync::atomic::{AtomicU64, Ordering};
let ptr = &self.value as *const f64 as *const AtomicU64;
f64::from_bits(unsafe { (*ptr).load(Ordering::Relaxed) })
} }
#[cfg(target_os = "cuda")] pub fn set(&self, val: f32) {
self.value self.bits.store(val.to_bits(), Ordering::Relaxed);
} }
pub fn add(&self, v: f64) { /// Atomically adds `val` to the current value.
let ptr = &self.value as *const f64 as *mut f64; /// Uses a Compare-And-Swap (CAS) loop.
pub fn add(&self, val: f32) {
#[cfg(target_os = "cuda")] let mut current_bits = self.bits.load(Ordering::Relaxed);
unsafe {
cuda_std::intrinsics::atomic_add(ptr, v);
}
#[cfg(not(target_os = "cuda"))]
unsafe {
use core::sync::atomic::{AtomicU64, Ordering};
let atomic_ptr = ptr as *const AtomicU64;
let atomic = &*atomic_ptr;
let mut current_bits = atomic.load(Ordering::Relaxed);
loop { loop {
let current_val = f64::from_bits(current_bits); let current_val = f32::from_bits(current_bits);
let new_val = current_val + v; let new_val = current_val + val;
match atomic.compare_exchange_weak( let new_bits = new_val.to_bits();
match self.bits.compare_exchange_weak(
current_bits, current_bits,
new_val.to_bits(), new_bits,
Ordering::Relaxed, Ordering::Relaxed,
Ordering::Relaxed, Ordering::Relaxed,
) { ) {
@ -84,5 +72,39 @@ impl AtomicFloat {
} }
} }
} }
}
pub struct AtomicDouble {
bits: AtomicU64,
}
impl AtomicDouble {
pub fn new(val: f64) -> Self {
Self {
bits: AtomicU64::new(val.to_bits()),
}
}
pub fn get(&self) -> f64 {
f64::from_bits(self.bits.load(Ordering::Relaxed))
}
pub fn add(&self, val: f64) {
let mut current_bits = self.bits.load(Ordering::Relaxed);
loop {
let current_val = f64::from_bits(current_bits);
let new_val = current_val + val;
let new_bits = new_val.to_bits();
match self.bits.compare_exchange_weak(
current_bits,
new_bits,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(x) => current_bits = x,
}
}
} }
} }

View file

@ -45,7 +45,7 @@ fn noise_weight(t: Float) -> Float {
fn grad(x: i32, y: i32, z: i32, dx: Float, dy: Float, dz: Float) -> Float { fn grad(x: i32, y: i32, z: i32, dx: Float, dy: Float, dz: Float) -> Float {
let hash = let hash =
NOISE_PERM[NOISE_PERM[NOISE_PERM[x as usize] as usize + y as usize] as usize + z as usize]; NOISE_PERM[NOISE_PERM[NOISE_PERM[x as usize] as usize + y as usize] as usize + z as usize];
let h = h & 15; let h = hash & 15;
let u = if h < 8 || h == 12 || h == 13 { dx } else { dy }; let u = if h < 8 || h == 12 || h == 13 { dx } else { dy };
let v = if h < 4 || h == 12 || h == 13 { dy } else { dz }; let v = if h < 4 || h == 12 || h == 13 { dy } else { dz };
@ -64,10 +64,20 @@ fn grad(x: i32, y: i32, z: i32, dx: Float, dy: Float, dz: Float) -> Float {
} }
#[inline(always)] #[inline(always)]
pub fn noise_from_point(mut p: Point3f) -> Float { pub fn noise_from_point(p: Point3f) -> Float {
noise(p.x(), p.y(), p.z()) noise(p.x(), p.y(), p.z())
} }
#[inline(always)]
pub fn noise_2d(x: Float, y: Float) -> Float {
noise(x, y, 0.5)
}
#[inline(always)]
pub fn noise_1d(x: Float) -> Float {
noise(x, 0.5, 0.5)
}
#[inline(always)] #[inline(always)]
pub fn noise(mut x: Float, mut y: Float, mut z: Float) -> Float { pub fn noise(mut x: Float, mut y: Float, mut z: Float) -> Float {
let max_coord = (1i32 << 30) as Float; let max_coord = (1i32 << 30) as Float;
@ -98,13 +108,13 @@ pub fn noise(mut x: Float, mut y: Float, mut z: Float) -> Float {
// Fetch gradients // Fetch gradients
let w000 = grad(ix, iy, iz, dx, dy, dz); let w000 = grad(ix, iy, iz, dx, dy, dz);
let w100 = grad(ix + 1, iy, iz, dx - 1, dy, dz); let w100 = grad(ix + 1, iy, iz, dx - 1., dy, dz);
let w010 = grad(ix, iy + 1, iz, dx, dy - 1, dz); let w010 = grad(ix, iy + 1, iz, dx, dy - 1., dz);
let w110 = grad(ix + 1, iy + 1, iz, dx - 1, dy - 1, dz); let w110 = grad(ix + 1, iy + 1, iz, dx - 1., dy - 1., dz);
let w001 = grad(ix, iy, iz + 1, dx, dy, dz - 1); let w001 = grad(ix, iy, iz + 1, dx, dy, dz - 1.);
let w101 = grad(ix + 1, iy, iz + 1, dx - 1, dy, dz - 1); let w101 = grad(ix + 1, iy, iz + 1, dx - 1., dy, dz - 1.);
let w011 = grad(ix, iy + 1, iz + 1, dx, dy - 1, dz - 1); let w011 = grad(ix, iy + 1, iz + 1, dx, dy - 1., dz - 1.);
let w111 = grad(ix + 1, iy + 1, iz + 1, dx - 1, dy - 1, dz - 1); let w111 = grad(ix + 1, iy + 1, iz + 1, dx - 1., dy - 1., dz - 1.);
let wx = noise_weight(dx); let wx = noise_weight(dx);
let wy = noise_weight(dy); let wy = noise_weight(dy);
@ -121,23 +131,25 @@ pub fn noise(mut x: Float, mut y: Float, mut z: Float) -> Float {
lerp(wz, y0, y1) lerp(wz, y0, y1)
} }
pub fn fbm(p: Point3f, dpdx: Vector3f, dpdy: Vector3f, omega: Float, max_octaves: usize) -> Float { pub fn fbm(p: Point3f, dpdx: Vector3f, dpdy: Vector3f, omega: Float, max_octaves: u32) -> Float {
// Compute number of octaves for antialiased FBm // Compute number of octaves for antialiased FBm
let len2 = dpdx.norm_squared().max(dpdy.norm_squared()); let len2 = dpdx.norm_squared().max(dpdy.norm_squared());
let n = clamp(-1. - len.log2() / 2., 0., max_octaves); let n = clamp(-1. - len2.log2() / 2., 0., max_octaves as Float);
let n_int = n.floor(); let n_int = n.floor() as usize;
let mut sum = 0.; let mut sum = 0.;
let mut lambda = 1.; let mut lambda = 1.;
let mut o = 1; let mut o = 1.;
for i in 0..n_int { for _ in 0..n_int {
sum += o * Noise(lambda * p); sum += o * noise_from_point(Point3f::from(lambda * Vector3f::from(p)));
lambda *= 1.99; lambda *= 1.99;
o *= omega; o *= omega;
} }
let n_partial = n - n_int; let n_partial = n - n_int as Float;
sum += o * smooth_step(n_partial, 0.3, 0.7) * noise_from_point(lambda * p); sum += o
* smooth_step(n_partial, 0.3, 0.7)
* noise_from_point(Point3f::from(lambda * Vector3f::from(p)));
return sum; return sum;
} }
@ -147,30 +159,30 @@ pub fn turbulence(
dpdx: Vector3f, dpdx: Vector3f,
dpdy: Vector3f, dpdy: Vector3f,
omega: Float, omega: Float,
max_octaves: usize, max_octaves: u32,
) -> Float { ) -> Float {
// Compute number of octaves for antialiased FBm // Compute number of octaves for antialiased FBm
let len2 = dpdx.norm_squared().max(dpdy.norm_squared()); let len2 = dpdx.norm_squared().max(dpdy.norm_squared());
let n = clamp(-1. - len2.log2() / 2., 0, maxOctaves); let n = clamp(-1. - len2.log2() / 2., 0., max_octaves as Float);
let n_int = n.floor(); let n_int = n.floor() as usize;
// Compute sum of octaves of noise for turbulence // Compute sum of octaves of noise for turbulence
let mut sum = 0.; let mut sum = 0.;
let mut lambda = 1.; let mut lambda = 1.;
let mut o = 1.; let mut o = 1.;
for i in 0..n_int { for _ in 0..n_int {
sum += o * noise_from_point(lambda * p).abs(); sum += o * noise_from_point(Point3f::from(lambda * Vector3f::from(p)));
lambda *= 1.99; lambda *= 1.99;
o *= omega; o *= omega;
} }
let n_partial = n - n_int; let n_partial = n - n_int as Float;
sum += o * lerp( sum += o * lerp(
smooth_step(n_partial, 0.3, 0.7), smooth_step(n_partial, 0.3, 0.7),
0.2, 0.2,
noise_from_point(lambda * p).abs(), noise_from_point(Point3f::from(lambda * Vector3f::from(p))).abs(),
); );
for i in n_int..max_octaves { for _ in n_int..max_octaves as usize {
sum += o * 0.2; sum += o * 0.2;
o *= omega; o *= omega;
} }

View file

@ -1,49 +1,190 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use core::ops::Index;
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
pub struct Ptr<T: ?Sized> { pub struct ArenaPtr<T: ?Sized> {
offset: isize, offset: i32,
_phantom: PhantomData<T>, _marker: PhantomData<T>,
} }
impl<T: ?Sized> Clone for Ptr<T> { impl<T: ?Sized> Clone for ArenaPtr<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
impl<T: ?Sized> Copy for Ptr<T> {} impl<T: ?Sized> Copy for ArenaPtr<T> {}
impl<T> Ptr<T> { impl<T> ArenaPtr<T> {
pub fn null() -> Self { pub fn null() -> Self {
Self { Self {
offset: 0, offset: 0xFFFFFFFF,
_phantom: PhantomData, _marker: PhantomData,
} }
} }
pub fn is_null(&self) -> Self { pub fn is_null(&self) -> bool {
self.offset = 0; self.offset == 0xFFFFFFFF
} }
pub fn get(&self) -> Option<&T> { #[inline(always)]
if self.offset == 0 { pub unsafe fn as_ptr(&self, base: *const u8) -> *const T {
None if self.is_null() {
core::ptr::null()
} else { } else {
unsafe { unsafe { base.add(self.offset as usize) as *const T }
let base = self as *const _ as *const u8;
let target = base.offset(self.offset) as *const T;
} }
} }
#[inline(always)]
pub unsafe fn as_ref<'a>(&self, base: *const u8) -> &'a T {
unsafe { &*self.as_ptr(base) }
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
pub fn new(me: *const Self, target: *const T) -> Self { pub fn new(me: *const Self, target: *const T) -> Self {
if target.is_null() {
return Self::null();
}
let diff = unsafe { (target as *const u8).offset_from(me as *const u8) }; let diff = unsafe { (target as *const u8).offset_from(me as *const u8) };
if diff > i32::MAX as isize || diff < i32::MIN as isize {
panic!("RelPtr offset out of i32 range! Are objects too far apart?");
}
Self { Self {
offset: diff, offset: diff as i32,
_phantom: PhantomData, _marker: PhantomData,
} }
} }
} }
#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct Ptr<T>(pub *const T);
impl<T> Default for Ptr<T> {
fn default() -> Self {
Self(core::ptr::null())
}
}
impl<T> PartialEq for Ptr<T> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for Ptr<T> {}
impl<T> Ptr<T> {
pub fn null() -> Self {
Self::default()
}
#[inline(always)]
pub fn is_null(&self) -> bool {
self.0.is_null()
}
#[inline(always)]
pub fn as_ref(&self) -> Option<&T> {
unsafe { self.0.as_ref() }
}
/// UNSTABLE: Casts the const pointer to mutable and returns a mutable reference.
/// THIS IS VERY DANGEROUS
/// The underlying data is not currently borrowed or accessed by any other thread/kernel.
/// The memory is actually writable.
/// No other mutable references exist to this data.
#[inline(always)]
pub unsafe fn as_mut(&mut self) -> &mut T {
debug_assert!(!self.is_null());
unsafe { &mut *(self.0 as *mut T) }
}
}
unsafe impl<T: Sync> Send for Ptr<T> {}
unsafe impl<T: Sync> Sync for Ptr<T> {}
impl<T> std::ops::Deref for Ptr<T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
unsafe { &*self.0 }
}
}
impl<T> From<&T> for Ptr<T> {
fn from(r: &T) -> Self {
Self(r as *const T)
}
}
impl<T> From<&mut T> for Ptr<T> {
fn from(r: &mut T) -> Self {
Self(r as *const T)
}
}
impl<T> Index<usize> for Ptr<T> {
type Output = T;
#[inline(always)]
fn index(&self, index: usize) -> &Self::Output {
// There is no bounds checking because we dont know the length.
// It is host responsbility to check bounds
unsafe { &*self.0.add(index) }
}
}
impl<T> Index<u32> for Ptr<T> {
type Output = T;
#[inline(always)]
fn index(&self, index: u32) -> &Self::Output {
unsafe { &*self.0.add(index as usize) }
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Slice<T> {
pub ptr: Ptr<T>,
pub len: u32,
pub _marker: PhantomData<T>,
}
unsafe impl<T: Sync> Send for Slice<T> {}
unsafe impl<T: Sync> Sync for Slice<T> {}
impl<T> Slice<T> {
pub fn new(ptr: Ptr<T>, len: u32) -> Self {
Self {
ptr,
len,
_marker: PhantomData,
}
}
pub fn as_ptr(&self) -> *const T {
self.ptr.0
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<T> Index<usize> for Slice<T> {
type Output = T;
#[inline(always)]
fn index(&self, index: usize) -> &Self::Output {
unsafe { &*self.ptr.0.add(index) }
}
}

View file

@ -1,7 +1,8 @@
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Rng { pub struct Rng {
state: u64, pub state: u64,
inc: u64, pub inc: u64,
} }
impl Default for Rng { impl Default for Rng {

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
use crate::core::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike}; use crate::core::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::math::lerp; use crate::utils::math::lerp;
use core::ops::Sub;
use num_traits::Num; use num_traits::Num;
fn bounds_cubic_bezier(cp: &[Point3f]) -> Bounds3f { fn bounds_cubic_bezier(cp: &[Point3f]) -> Bounds3f {
@ -39,7 +40,7 @@ where
} }
pub fn subdivide_cubic_bezier(cp: &[Point3f]) -> [Point3f; 7] { pub fn subdivide_cubic_bezier(cp: &[Point3f]) -> [Point3f; 7] {
let v: Vec<Vector3f> = cp.iter().map(|&p| p.into()).collect(); let v: [Vector3f; 4] = core::array::from_fn(|i| Vector3f::from(cp[i]));
let v01 = (v[0] + v[1]) / 2.0; let v01 = (v[0] + v[1]) / 2.0;
let v12 = (v[1] + v[2]) / 2.0; let v12 = (v[1] + v[2]) / 2.0;
let v23 = (v[2] + v[3]) / 2.0; let v23 = (v[2] + v[3]) / 2.0;
@ -59,7 +60,49 @@ pub fn subdivide_cubic_bezier(cp: &[Point3f]) -> [Point3f; 7] {
] ]
} }
pub fn evaluate_cubic_bezier(cp: &[Point3f], u: Float) -> (Point3f, Vector3f) { pub fn elevate_quadratic_bezier_to_cubic(cp: &[Point3f]) -> [Point3f; 4] {
[
cp[0],
lerp(2. / 3., cp[0], cp[1]),
lerp(1. / 3., cp[1], cp[2]),
cp[2],
]
}
pub fn cubic_bspline_to_bezier(cp: &[Point3f]) -> [Point3f; 4] {
// Blossom from p012, p123, p234, and p345 to the Bezier control points
// p222, p223, p233, and p333.
// https://people.eecs.berkeley.edu/~sequin/CS284/IMGS/cubicbsplinepoints.gif
let p012 = cp[0];
let p123 = cp[1];
let p234 = cp[2];
let p345 = cp[3];
let p122 = lerp(2. / 3., p012, p123);
let p223 = lerp(1. / 3., p123, p234);
let p233 = lerp(2. / 3., p123, p234);
let p334 = lerp(1. / 3., p234, p345);
let p222 = lerp(0.5, p122, p223);
let p333 = lerp(0.5, p233, p334);
[p222, p223, p233, p333]
}
pub fn quadratic_bspline_to_bezier(cp: &[Point3f]) -> [Point3f; 3] {
// We can compute equivalent Bezier control points via some blossoming.
// We have three control points and a uniform knot vector; we will label
// the points p01, p12, and p23. We want the Bezier control points of
// the equivalent curve, which are p11, p12, and p22. We already have
// p12.
let p11 = lerp(0.5, cp[0], cp[1]);
let p22 = lerp(0.5, cp[1], cp[2]);
[p11, cp[1], p22]
}
pub fn evaluate_cubic_bezier(cp: &[Point3f], u: Float) -> (Point3f, Vector3f)
where
{
let cp1 = [ let cp1 = [
lerp(u, cp[0], cp[1]), lerp(u, cp[0], cp[1]),
lerp(u, cp[1], cp[2]), lerp(u, cp[1], cp[2]),

View file

@ -1,6 +1,4 @@
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
use std::error::Error;
use std::fmt::{self, Display};
use std::iter::{Product, Sum}; use std::iter::{Product, Sum};
use std::ops::{Add, Div, Index, IndexMut, Mul}; use std::ops::{Add, Div, Index, IndexMut, Mul};
use std::sync::Arc; use std::sync::Arc;
@ -13,7 +11,7 @@ use crate::core::geometry::{
VectorLike, VectorLike,
}; };
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction, Interaction, InteractionBase, InteractionTrait, MediumInteraction, SurfaceInteraction,
}; };
use crate::core::pbrt::{Float, gamma}; use crate::core::pbrt::{Float, gamma};
use crate::utils::error::InversionError; use crate::utils::error::InversionError;
@ -168,7 +166,7 @@ impl TransformGeneric<Float> {
*t -= dt; *t -= dt;
} }
} }
Ray::new(o.into(), r.d, Some(r.time), r.medium.clone()) Ray::new(o.into(), r.d, Some(r.time), &*r.medium)
} }
pub fn apply_to_interval(&self, pi: &Point3fi) -> Point3fi { pub fn apply_to_interval(&self, pi: &Point3fi) -> Point3fi {
@ -263,7 +261,7 @@ impl TransformGeneric<Float> {
ret.shading.dndu = self.apply_to_normal(si.shading.dndu); ret.shading.dndu = self.apply_to_normal(si.shading.dndu);
ret.shading.dndv = self.apply_to_normal(si.shading.dndv); ret.shading.dndv = self.apply_to_normal(si.shading.dndv);
ret.common.n = n.normalize().face_forward(ret.shading.n.into()); ret.common.n = n.normalize().face_forward(ret.shading.n);
Interaction::Surface(ret) Interaction::Surface(ret)
} }
@ -369,10 +367,7 @@ impl TransformGeneric<Float> {
t = t_max - dt; t = t_max - dt;
} }
} }
( (Ray::new(Point3f::from(o), d, Some(r.time), &*r.medium), t)
Ray::new(Point3f::from(o), d, Some(r.time), r.medium.clone()),
t,
)
} }
pub fn to_quaternion(self) -> Quaternion { pub fn to_quaternion(self) -> Quaternion {
@ -820,7 +815,7 @@ impl AnimatedTransform {
actually_animated: false, actually_animated: false,
t: [Vector3f::default(); 2], t: [Vector3f::default(); 2],
r: [Quaternion::default(); 2], r: [Quaternion::default(); 2],
s: std::array::from_fn(|_| SquareMatrix::default()), s: core::array::from_fn(|_| SquareMatrix::default()),
has_rotation: false, has_rotation: false,
c1: [DerivativeTerm::default(); 3], c1: [DerivativeTerm::default(); 3],
c2: [DerivativeTerm::default(); 3], c2: [DerivativeTerm::default(); 3],

View file

@ -1,7 +1,7 @@
use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
use crate::core::pbrt::{Float, find_interval}; use crate::core::pbrt::{Float, find_interval};
use crate::core::primitive::PrimitiveTrait; use crate::core::primitive::PrimitiveTrait;
use crate::shapes::ShapeIntersection; use crate::core::shape::ShapeIntersection;
use crate::utils::math::encode_morton_3; use crate::utils::math::encode_morton_3;
use crate::utils::math::next_float_down; use crate::utils::math::next_float_down;
use crate::utils::partition_slice; use crate::utils::partition_slice;

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

@ -0,0 +1,34 @@
pub struct BSSRDFTableData {
pub rho_samples: Vec<Float>,
pub radius_samples: Vec<Float>,
pub profile: Vec<Float>,
pub rho_eff: Vec<Float>,
pub profile_cdf: Vec<Float>,
}
impl BSSRDFTableData {
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 view(&self, rho_ptr: *const f32, radius_ptr: *const f32) -> BSSRDFTableView {
BSSRDFTable {
rho_samples: rho_ptr,
n_rho: self.rho_samples.len() as u32,
radius_samples: radius_ptr,
n_radius: self.radius_samples.len() as u32,
// ...
}
}
}

Some files were not shown because too many files have changed in this diff Show more