diff --git a/shared/src/core/bssrdf.rs b/shared/src/core/bssrdf.rs index 3670467..d08be08 100644 --- a/shared/src/core/bssrdf.rs +++ b/shared/src/core/bssrdf.rs @@ -251,7 +251,7 @@ impl TabulatedBSSRDF { sr += weight * self .table - .eval_profile(rho_offset + j as u32, radius_offset + k as u32); + .eval_profile((rho_offset + j as i32) as u32, (radius_offset + k as i32) as u32); } } } @@ -302,14 +302,14 @@ impl TabulatedBSSRDF { for (j, rho_weight) in rho_weights.iter().enumerate() { if *rho_weight != 0. { // Update _rhoEff_ and _sr_ for wavelength - rho_eff += rhoeff_samples[rho_offset as usize + j] * rho_weight; + rho_eff += rhoeff_samples[(rho_offset + j as i32) as usize] * rho_weight; // Fix: Use .iter().enumerate() for 'k' for (k, radius_weight) in radius_weights.iter().enumerate() { if *radius_weight != 0. { sr += self .table - .eval_profile(rho_offset + j as u32, radius_offset + k as u32) + .eval_profile((rho_offset + j as i32) as u32, (radius_offset + k as i32) as u32) * rho_weight * radius_weight; } diff --git a/shared/src/core/color.rs b/shared/src/core/color.rs index 24d1ba0..9f38595 100644 --- a/shared/src/core/color.rs +++ b/shared/src/core/color.rs @@ -625,7 +625,8 @@ impl RGBSigmoidPolynomial { } pub fn evaluate(&self, lambda: Float) -> Float { - let eval = evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]); + // pbrt: `s(EvaluatePolynomial(lambda, c2, c1, c0))` -- c2 is the constant term. + let eval = evaluate_polynomial(lambda, &[self.c2, self.c1, self.c0]); Self::s(eval) } diff --git a/shared/src/core/sampler.rs b/shared/src/core/sampler.rs index 2efe4fd..5238d0a 100644 --- a/shared/src/core/sampler.rs +++ b/shared/src/core/sampler.rs @@ -192,15 +192,18 @@ impl SamplerTrait for HaltonSampler { } fn get1d(&mut self) -> Float { - if self.dim + 1 >= PRIME_TABLE_SIZE as u32 { + // pbrt: `SampleDimension(dimension++)` -- POST-increment. Pre-incrementing makes + // the next Get2D() reuse the dimension this call just consumed. + if self.dim >= PRIME_TABLE_SIZE as u32 { self.dim = 2; } + let dim = self.dim; self.dim += 1; - self.sample_dimension(self.dim) + self.sample_dimension(dim) } fn get2d(&mut self) -> Point2f { - if self.dim > PRIME_TABLE_SIZE as u32 { + if self.dim + 1 >= PRIME_TABLE_SIZE as u32 { self.dim = 2; } let dim = self.dim; @@ -301,8 +304,9 @@ impl SamplerTrait for StratifiedSampler { hash as u32, ); self.dim += 2; + // pbrt: both the modulus and the divisor are xPixelSamples. let x = stratum % self.x_pixel_samples as u32; - let y = stratum / self.y_pixel_samples as u32; + let y = stratum / self.x_pixel_samples as u32; let dx = if self.jitter { self.rng.uniform::() } else { @@ -387,6 +391,7 @@ impl SamplerTrait for PaddedSobolSampler { self.samples_per_pixel as u32, hash as u32, ); + self.dim += 1; self.sample_dimension(0, index, (hash >> 32) as u32) } fn get2d(&mut self) -> Point2f { @@ -512,7 +517,7 @@ impl SamplerTrait for SobolSampler { ) as Float; u[1] = clamp( u[1] * self.scale as Float - self.pixel[1] as Float, - 1., + 0., ONE_MINUS_EPSILON, ) as Float; u diff --git a/shared/src/shapes/triangle.rs b/shared/src/shapes/triangle.rs index c002719..15f1dd1 100644 --- a/shared/src/shapes/triangle.rs +++ b/shared/src/shapes/triangle.rs @@ -1,8 +1,8 @@ -use crate::core::geometry::{spherical_triangle_area, SqrtExt, Tuple, VectorLike}; use crate::core::geometry::{ Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3, Vector3f, }; +use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area}; use crate::core::interaction::{ Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction, }; @@ -13,7 +13,7 @@ use crate::utils::sampling::{ bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle, sample_uniform_triangle, }; -use crate::{gamma, Float, GVec, Ptr}; +use crate::{Float, GVec, Ptr, gamma}; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -198,34 +198,34 @@ impl TriangleShape { // Ensure that computed triangle $t$ is conservatively greater than zero // Compute $\delta_z$ term for triangle $t$ error bounds - let maxZt = Vector3f::new(p0t.z(), p1t.z(), p2t.z()) + let max_zt = Vector3f::new(p0t.z(), p1t.z(), p2t.z()) .abs() .max_component_value(); - let deltaZ = gamma(3) * maxZt; + let delta_z = gamma(3) * max_zt; // Compute $\delta_x$ and $\delta_y$ terms for triangle $t$ error bounds - let maxXt = Vector3f::new(p0t.x(), p1t.x(), p2t.x()) + let max_xt = Vector3f::new(p0t.x(), p1t.x(), p2t.x()) .abs() .max_component_value(); - let maxYt = Vector3f::new(p0t.y(), p1t.y(), p2t.y()) + let max_yt = Vector3f::new(p0t.y(), p1t.y(), p2t.y()) .abs() .max_component_value(); - let deltaX = gamma(5) * (maxXt + maxZt); - let deltaY = gamma(5) * (maxYt + maxZt); + let delta_x = gamma(5) * (max_xt + max_zt); + let delta_y = gamma(5) * (max_yt + max_zt); // Compute $\delta_e$ term for triangle $t$ error bounds - let deltaE = 2. * (gamma(2) * maxXt * maxYt + deltaY * maxXt + deltaX * maxYt); + let delta_e = 2. * (gamma(2) * max_xt * max_yt + delta_y * max_xt + delta_x * max_yt); // Compute $\delta_t$ term for triangle $t$ error bounds and check _t_ - let maxE = Vector3f::new(e0, e1, e2).abs().max_component_value(); - let deltaT = - 3. * (gamma(3) * maxE * maxZt + deltaE * maxZt + deltaZ * maxE) * inv_det.abs(); - if t <= deltaT { + let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value(); + let delta_t = + 3. * (gamma(3) * max_e * max_zt + delta_e * max_zt + delta_z * max_e) * inv_det.abs(); + if t <= delta_t { return None; } // Return _TriangleIntersection_ for intersection - return Some(TriangleIntersection { b0, b1, b2, t }); + Some(TriangleIntersection { b0, b1, b2, t }) } fn interaction_from_intersection( @@ -345,11 +345,11 @@ impl TriangleShape { let mut ts = ns.cross(ss.into()); if ts.norm_squared() > 0.0 { - ss = ts.cross(ns.into()).into(); + ss = ts.cross(ns).into(); } else { let (s, t) = ns.coordinate_system(); ss = s.into(); - ts = t.into(); + ts = t; } let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() { @@ -539,7 +539,8 @@ impl ShapeTrait for TriangleShape { fn intersect(&self, ray: &Ray, t_max: Option) -> Option { let [p0, p1, p2] = self.get_points(); - let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY), p0, p1, p2)?; + let tri_isect = + self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY), p0, p1, p2)?; let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d); Some(ShapeIntersection::new(intr, tri_isect.t)) } diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index 711c458..1795942 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -61,11 +61,14 @@ where T::lerp(t, a, b) } +/// pbrt `EvaluatePolynomial(t, c, cRemaining...) = FMA(t, EvaluatePolynomial(t, cRemaining...), c)`. +/// `coeffs[0]` is the *constant* term and `coeffs[n-1]` the highest-degree one, so +/// evaluation folds from the back. #[inline] pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float { assert!(!coeffs.is_empty()); - let mut result = coeffs[0]; - for &c in &coeffs[1..] { + let mut result = coeffs[coeffs.len() - 1]; + for &c in coeffs[..coeffs.len() - 1].iter().rev() { result = num_traits::Float::mul_add(t, result, c); } result @@ -96,7 +99,7 @@ where #[inline] pub fn safe_sqrt(x: Float) -> Float { - assert!(x > -1e-3); + debug_assert!(x > -1e-3); 0.0_f32.max(x).sqrt() } @@ -104,20 +107,14 @@ pub fn safe_sqrt(x: Float) -> Float { pub fn safe_asin(x: T) -> T { let epsilon = T::from(0.0001).unwrap(); let one = T::one(); - if x >= -(one + epsilon) && x <= one + epsilon { - clamp(x, -one, one).asin() - } else { - panic!("Not valid value for asin") - } + debug_assert!(x >= -(one + epsilon) && x <= one + epsilon); + clamp(x, -one, one).asin() } #[inline] pub fn safe_acos(x: Float) -> Float { - if (-1.001..1.001).contains(&x) { - clamp(x, -1., 1.).acos() - } else { - panic!("Not valid value for acos") - } + debug_assert!((-1.001..1.001).contains(&x)); + clamp(x, -1., 1.).acos() } #[inline] @@ -351,9 +348,10 @@ pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f { pub fn integrate_catmull_rom(nodes: &[Float], f: &[Float], cdf: &mut [Float]) -> Float { debug_assert_eq!(nodes.len(), f.len()); + debug_assert_eq!(cdf.len(), nodes.len()); let mut sum = 0.; cdf[0] = 0.; - for i in 0..nodes.len() { + for i in 0..nodes.len() - 1 { let x0 = nodes[i]; let x1 = nodes[i + 1]; let f0 = f[i]; @@ -434,7 +432,7 @@ pub fn invert_catmull_rom(nodes: &[Float], f: &[Float], u: Float) -> Float { return x0 + t * width; } -pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4])> { +pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(i32, [Float; 4])> { if nodes.len() < 4 { return None; } @@ -452,7 +450,10 @@ pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4] // Safety clamp (though bounds check above handles most cases) let idx = idx.min(nodes.len() - 2); - let offset = idx.saturating_sub(1); // The C++ code uses idx - 1 for the offset + // pbrt: `*offset = idx - 1`, which is legitimately -1 when idx == 0. weights[0] is + // then 0, so callers never dereference the out-of-range slot -- but the remaining + // three weights must still line up with nodes[idx-1+i]. + let offset = idx as i32 - 1; let x0 = nodes[idx]; let x1 = nodes[idx + 1]; @@ -491,7 +492,7 @@ pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4] weights[3] = 0.0; } - Some((offset as u32, weights)) + Some((offset, weights)) } pub fn equal_area_sphere_to_square(d: Vector3f) -> Point2f { @@ -546,7 +547,7 @@ pub fn equal_area_square_to_sphere(p: Point2f) -> Vector3f { // Compute angle \phi for square to sphere mapping let mut phi = if r == 0. { 1. } else { (vp - up) / r + 1. }; - phi /= PI_OVER_4; + phi *= PI_OVER_4; // Find z for spherical direction let z = (1. - square(r)).copysign(signed_distance); @@ -585,12 +586,13 @@ pub fn gaussian_integral(x0: Float, x1: Float, mu: Float, sigma: Float) -> Float 0.5 * (erf((mu - x0) / sigma_root2) - erf((mu - x1) / sigma_root2)) } +#[inline(always)] pub fn sample_linear(u: Float, a: Float, b: Float) -> Float { assert!(a >= 0. && b >= 0.); if u == 0. && a == 0. { return 0.; } - let x = u * (a + b) / (a + (lerp(u, square(a), square(b)))); + let x = u * (a + b) / (a + lerp(u, square(a), square(b)).sqrt()); x.min(ONE_MINUS_EPSILON) } @@ -700,12 +702,13 @@ pub fn sample_discrete( } pub fn sample_tent(u: Float, r: Float) -> Float { + // pbrt rewrites `u` in place with the remapped sample and feeds *that* to SampleLinear. let mut u_remapped = 0.0; let offset = sample_discrete(&[0.5, 0.5], u, None, Some(&mut u_remapped)); if offset == 0 { - -r + r * sample_linear(u, 0., 1.) + -r + r * sample_linear(u_remapped, 0., 1.) } else { - r * sample_linear(u, 1., 0.) + r * sample_linear(u_remapped, 1., 0.) } } @@ -735,7 +738,7 @@ pub fn encode_morton_2(x: u32, y: u32) -> u64 { } pub fn encode_morton_3(x: Float, y: Float, z: Float) -> u32 { - (left_shift3(x as u32) << 2) | (left_shift3(y as u32) << 1) | left_shift3(z as u32) + (left_shift3(z as u32) << 2) | (left_shift3(y as u32) << 1) | left_shift3(x as u32) } pub fn round_up_pow2(mut n: i32) -> i32 { @@ -1590,7 +1593,7 @@ where } let det: T = (0..N).map(|i| lum[i][i]).product(); - if parity < 0 { -det } else { det } + if parity % 2 == 1 { -det } else { det } } } } @@ -1723,7 +1726,9 @@ pub fn f16_to_f32_software(h: u16) -> f32 { m <<= 1; e += 1; } - (sign << 31) | ((112 - e) << 23) | ((m & 0x3FF) << 13) + // half subnormals have exponent -14; after `e` normalising shifts the + // f32 biased exponent is (-14 - e) + 127 = 113 - e. + (sign << 31) | ((113 - e) << 23) | ((m & 0x3FF) << 13) } } else if exp == 0x1F { (sign << 31) | (0xFF << 23) | (mant << 13) diff --git a/shared/src/utils/sampling.rs b/shared/src/utils/sampling.rs index 92295d1..60e34bb 100644 --- a/shared/src/utils/sampling.rs +++ b/shared/src/utils/sampling.rs @@ -52,7 +52,7 @@ pub fn invert_linear_sample(x: Float, a: Float, b: Float) -> Float { pub fn sample_bilinear(u: Point2f, w: &[Float]) -> Point2f { let y = sample_linear(u[1], w[0] + w[1], w[2] + w[3]); - let x = sample_linear(u[1], lerp(y, w[0], w[2]), lerp(y, w[1], w[3])); + let x = sample_linear(u[0], lerp(y, w[0], w[2]), lerp(y, w[1], w[3])); Point2f::new(x, y) } @@ -333,7 +333,8 @@ pub fn invert_spherical_rectangle_sample( } pub fn sample_exponential(u: Float, a: Float) -> Float { - (1. - u).ln() / a + debug_assert!(a > 0.); + -(1. - u).ln() / a } pub fn sample_spherical_triangle( @@ -550,7 +551,9 @@ pub fn sample_catmull_rom_2d( let mut v = 0.; for i in 0..4 { if weights[i] != 0. { - let ind = (offset + i as u32) * n2 + idx; + // `offset` is pbrt's `idx - 1` and may be -1; the matching weight is + // then zero, so the slot is never actually read. + let ind = (offset + i as i32) * n2 as i32 + idx as i32; v += array[ind as usize] * weights[i]; } } @@ -658,10 +661,10 @@ pub struct VarianceEstimator { impl VarianceEstimator { pub fn add(&mut self, x: Float) { self.n += 1; - let delta = x / self.mean; + let delta = x - self.mean; self.mean += delta / self.n as Float; let delta2 = x - self.mean; - self.s *= delta * delta2; + self.s += delta * delta2; } pub fn mean(&self) -> Float { @@ -688,7 +691,7 @@ impl VarianceEstimator { if ve.n != 0 { self.s += ve.s + square(ve.mean - self.mean) * (self.n * ve.n) as Float / (self.n + ve.n) as Float; - self.mean += + self.mean = (self.n as Float * self.mean + ve.n as Float * ve.mean) / (self.n + ve.n) as Float; self.n += ve.n; } @@ -719,24 +722,37 @@ impl PiecewiseConstant1D { } pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self { + debug_assert!(max > min); let n = f.len(); + + // pbrt takes the absolute value of the function before integrating, so filters + // with negative lobes (Mitchell, LanczosSinc) still give a usable CDF. + let mut func: GVec = gvec_with_capacity(n); + for &v in f { + func.push(v.abs()); + } + let mut cdf = gvec_with_capacity(n + 1); cdf.push(0.0); let delta = (max - min) / n as Float; for i in 0..n { - cdf.push(cdf[i] + f[i] * delta); + cdf.push(cdf[i] + func[i] * delta); } let func_integral = cdf[n]; - if func_integral > 0.0 { - for c in &mut cdf { + if func_integral == 0.0 { + for (i, c) in cdf.iter_mut().enumerate().skip(1) { + *c = i as Float / n as Float; + } + } else { + for c in cdf.iter_mut().skip(1) { *c /= func_integral; } } Self { - func: gvec_from_slice(f), + func, cdf, min, max, @@ -748,7 +764,6 @@ impl PiecewiseConstant1D { where F: Fn(Float) -> Float, { - let delta = (max - min) / n as Float; let delta = (max - min) / n as Float; let mut values = gvec_with_capacity(n); for i in 0..n { @@ -816,6 +831,7 @@ impl PiecewiseConstant1D { pub struct PiecewiseConstant2D { pub conditionals: GVec, pub marginal: PiecewiseConstant1D, + pub domain: Bounds2f, pub n_u: u32, pub n_v: u32, } @@ -852,6 +868,7 @@ impl PiecewiseConstant2D { Self { conditionals, marginal, + domain, n_u: n_u.try_into().unwrap(), n_v: n_v.try_into().unwrap(), } @@ -876,7 +893,10 @@ impl PiecewiseConstant2D { self.marginal.integral() } - pub fn pdf(&self, p: Point2f) -> Float { + pub fn pdf(&self, pr: Point2f) -> Float { + // pbrt maps the query point into the unit domain first; the distribution's + // domain is only [0,1]^2 for image distributions, not e.g. filter samplers. + let p = Point2f::from(self.domain.offset(&pr)); let u_offset = ((p.x() * self.n_u as Float) as usize).min(self.n_u as usize - 1); let v_offset = ((p.y() * self.n_v as Float) as usize).min(self.n_v as usize - 1); let conditional = unsafe { &*self.conditionals.as_ptr().add(v_offset) }; diff --git a/src/core/material.rs b/src/core/material.rs index 54b0438..51fee12 100644 --- a/src/core/material.rs +++ b/src/core/material.rs @@ -81,16 +81,15 @@ impl MaterialFactory for Material { SubsurfaceMaterial::create(parameters, normal_map, named_materials, &loc, arena) } "mix" => MixMaterial::create(parameters, normal_map, named_materials, &loc, arena), - _ => Err(anyhow!("Material type '{}' unknown at {}", name, &loc)), } } } pub fn default_diffuse_material(arena: &Arena) -> Material { - use shared::core::texture::SpectrumTexture; - use shared::core::texture::SpectrumConstantTexture; use shared::core::spectrum::{ConstantSpectrum, Spectrum}; + use shared::core::texture::SpectrumConstantTexture; + use shared::core::texture::SpectrumTexture; use shared::materials::DiffuseMaterial; use shared::utils::Ptr; diff --git a/src/materials/coated.rs b/src/materials/coated.rs index 13dbccf..05eef4a 100644 --- a/src/materials/coated.rs +++ b/src/materials/coated.rs @@ -24,10 +24,7 @@ impl CreateMaterial for CoatedDiffuseMaterial { arena: &Arena, ) -> Result { let reflectance = parameters - .get_spectrum_texture("reflectance", None, SpectrumType::Albedo) - .unwrap_or(Arc::new(SpectrumTexture::Constant( - SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))), - ))); + .get_spectrum_texture("reflectance", 0.5, SpectrumType::Albedo); let u_roughness = parameters.get_float_texture_with_fallback("uroughness", "roughness", 0.5)?; @@ -48,11 +45,7 @@ impl CreateMaterial for CoatedDiffuseMaterial { let n_samples = parameters.get_one_int("nsamples", 1)?; let g = parameters.get_float_texture("g", 0.)?; let albedo = parameters - .get_spectrum_texture("albedo", None, SpectrumType::Albedo) - .unwrap_or_else(|| { - let default_spectrum = Spectrum::Constant(ConstantSpectrum::new(0.)); - SpectrumTexture::Constant(SpectrumConstantTexture::new(default_spectrum)).into() - }); + .get_spectrum_texture("albedo", 0., SpectrumType::Albedo); let displacement = parameters.get_float_texture("displacement", 0.)?; let remap_roughness = parameters.get_one_bool("remaproughness", true)?; @@ -146,11 +139,7 @@ impl CreateMaterial for CoatedConductorMaterial { let n_samples = parameters.get_one_int("nsamples", 1)?; let g = parameters.get_float_texture("g", 0.)?; let albedo = parameters - .get_spectrum_texture("albedo", None, SpectrumType::Albedo) - .unwrap_or_else(|| { - let spectrum = Spectrum::Constant(ConstantSpectrum::new(0.)); - SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)).into() - }); + .get_spectrum_texture("albedo", 0., SpectrumType::Albedo); let displacement = parameters.get_float_texture_or_null("displacement")?; let remap_roughness = parameters.get_one_bool("remaproughness", true)?; diff --git a/src/materials/complex.rs b/src/materials/complex.rs index 5a38336..6836df3 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -240,8 +240,7 @@ impl CreateMaterial for SubsurfaceMaterial { Some(r) => { let one = Spectrum::Constant(ConstantSpectrum::new(1.)); let mfp = parameters - .get_spectrum_texture("mfp", Some(one), SpectrumType::Unbounded) - .expect("default supplied"); + .get_spectrum_texture("mfp", one, SpectrumType::Unbounded); SubsurfaceScattering::Reflectance { reflectance: arena.upload(r), mfp: arena.upload(mfp), diff --git a/src/materials/dielectric.rs b/src/materials/dielectric.rs index cb1dd8b..c4845b9 100644 --- a/src/materials/dielectric.rs +++ b/src/materials/dielectric.rs @@ -52,7 +52,7 @@ impl CreateMaterial for ThinDielectricMaterial { parameters: &TextureParameterDictionary, normal_map: Option>, _named_materials: &HashMap, - loc: &FileLoc, + _loc: &FileLoc, arena: &Arena, ) -> Result { let eta = if let Some(&v) = parameters.get_float_array("eta")?.first() { diff --git a/src/materials/diffuse.rs b/src/materials/diffuse.rs index de64a43..fff257f 100644 --- a/src/materials/diffuse.rs +++ b/src/materials/diffuse.rs @@ -1,9 +1,9 @@ +use crate::Arena; use crate::core::image::HostImage; use crate::core::material::CreateMaterial; use crate::core::texture::SpectrumTexture; use crate::utils::upload::ArenaUpload; use crate::utils::{FileLoc, TextureParameterDictionary}; -use crate::Arena; use anyhow::Result; use shared::core::material::Material; use shared::core::spectrum::Spectrum; @@ -22,13 +22,7 @@ impl CreateMaterial for DiffuseMaterial { _loc: &FileLoc, arena: &Arena, ) -> Result { - let reflectance = parameters - .get_spectrum_texture("reflectance", None, SpectrumType::Albedo) - .unwrap_or_else(|| { - Arc::new(SpectrumTexture::Constant( - SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))), - )) - }); + let reflectance = parameters.get_spectrum_texture("reflectance", 0.5, SpectrumType::Albedo); let displacement = parameters.get_float_texture_or_null("displacement")?; let specific = DiffuseMaterial { @@ -42,12 +36,26 @@ impl CreateMaterial for DiffuseMaterial { impl CreateMaterial for DiffuseTransmissionMaterial { fn create( - _parameters: &TextureParameterDictionary, - _normal_map: Option>, + parameters: &TextureParameterDictionary, + normal_map: Option>, _named_materials: &HashMap, _loc: &FileLoc, - _arena: &Arena, + arena: &Arena, ) -> Result { - todo!() + let reflectance = + parameters.get_spectrum_texture("reflectance", 0.25, SpectrumType::Albedo); + let transmittance = + parameters.get_spectrum_texture("transmittance", 0.25, SpectrumType::Albedo); + + let displacement = parameters.get_float_texture_or_null("displacement")?; + let scale = parameters.get_one_float("scale", 1.)?; + let mat = DiffuseTransmissionMaterial { + normal_map: arena.upload(normal_map), + displacement: arena.upload(displacement), + reflectance: arena.upload(reflectance), + transmittance: arena.upload(transmittance), + scale, + }; + Ok(Material::DiffuseTransmission(mat)) } } diff --git a/src/materials/mix.rs b/src/materials/mix.rs index 506e609..22cbc8a 100644 --- a/src/materials/mix.rs +++ b/src/materials/mix.rs @@ -1,7 +1,9 @@ use crate::core::image::HostImage; use crate::core::material::CreateMaterial; -use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; +use crate::{Arena, ArenaUpload, FileLoc, utils::TextureParameterDictionary}; use anyhow::Result; +use anyhow::anyhow; +use shared::Ptr; use shared::core::material::Material; use shared::materials::MixMaterial; use std::collections::HashMap; @@ -9,12 +11,31 @@ use std::sync::Arc; impl CreateMaterial for MixMaterial { fn create( - _parameters: &TextureParameterDictionary, + parameters: &TextureParameterDictionary, _normal_map: Option>, - _named_materials: &HashMap, - _loc: &FileLoc, - _arena: &Arena, + named_materials: &HashMap, + loc: &FileLoc, + arena: &Arena, ) -> Result { - todo!() + let names = parameters.get_string_array("materials")?; + let [a, b]: [String; 2] = names + .try_into() + .map_err(|_| anyhow!("{loc}: must provide two values for \"string materials\""))?; + + let mut resolve = |n: &str| -> Result> { + let m = named_materials + .get(n) + .ok_or_else(|| anyhow!("{loc}: {n}: named material not found"))?; + Ok(arena.alloc(*m)) + }; + + let materials = [resolve(&a)?, resolve(&b)?]; + let amount = parameters.get_float_texture("amount", 0.5)?; + + let mix_mat = MixMaterial { + materials, + amount: arena.upload(amount), + }; + Ok(Material::Mix(mix_mat)) } } diff --git a/src/textures/checkerboard.rs b/src/textures/checkerboard.rs index c908514..e15cee1 100644 --- a/src/textures/checkerboard.rs +++ b/src/textures/checkerboard.rs @@ -64,8 +64,7 @@ impl CreateSpectrumTexture for SpectrumCheckerboardTexture { let tex = |name: &str, def: Spectrum| { arena.upload( parameters - .get_spectrum_texture(name, Some(def), spectrum_type) - .expect("default supplied"), + .get_spectrum_texture(name, def, spectrum_type), ) }; diff --git a/src/textures/dots.rs b/src/textures/dots.rs index f35f7be..be137ad 100644 --- a/src/textures/dots.rs +++ b/src/textures/dots.rs @@ -47,8 +47,7 @@ impl CreateSpectrumTexture for SpectrumDotsTexture { let get = |name: &str, def: Spectrum| { let t = parameters - .get_spectrum_texture(name, Some(def), spectrum_type) - .expect("default supplied"); + .get_spectrum_texture(name, def, spectrum_type); arena.upload(t) }; let tex = SpectrumDotsTexture::new(map, get("inside", one), get("outside", zero)); diff --git a/src/textures/mix.rs b/src/textures/mix.rs index 97ce551..d8d0c9c 100644 --- a/src/textures/mix.rs +++ b/src/textures/mix.rs @@ -87,8 +87,7 @@ impl CreateSpectrumTexture for SpectrumMixTexture { let tex = |name: &str, def: Spectrum| { parameters - .get_spectrum_texture(name, Some(def), spectrum_type) - .expect("default supplied") + .get_spectrum_texture(name, def, spectrum_type) }; let tex = SpectrumMixTexture { @@ -119,8 +118,7 @@ impl CreateSpectrumTexture for SpectrumDirectionMixTexture { let dir = render_from_texture.apply_to_vector(dir_raw).normalize(); let tex = |name: &str, def: Spectrum| { parameters - .get_spectrum_texture(name, Some(def), spectrum_type) - .expect("default supplied") + .get_spectrum_texture(name, def, spectrum_type) }; let zero = Spectrum::Constant(ConstantSpectrum::new(0.)); diff --git a/src/textures/scaled.rs b/src/textures/scaled.rs index c798d75..2a45971 100644 --- a/src/textures/scaled.rs +++ b/src/textures/scaled.rs @@ -66,9 +66,8 @@ impl CreateSpectrumTexture for SpectrumScaledTexture { _arena: &Arena, ) -> Result { let one = Spectrum::Constant(ConstantSpectrum::new(1.0)); - let tex = parameters - .get_spectrum_texture("tex", Some(one), spectrum_type) - .ok_or_else(|| anyhow::anyhow!("{}: missing \"tex\" parameter", _loc))?; + // pbrt defaults "tex" to 1.0 rather than erroring (textures.cpp). + let tex = parameters.get_spectrum_texture("tex", one, spectrum_type); let scale = parameters.get_float_texture("scale", 1.0)?; if let FloatTexture::Constant(ref cscale) = *scale { diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index 0fbaffe..cae920a 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -835,22 +835,18 @@ impl TextureParameterDictionary { self.dict.report_unused() } + /// Fetch a spectrum texture, falling back to a constant built from `def`. + /// A default is always supplied, so this cannot fail -- mirrors + /// `get_float_texture`. Use `get_spectrum_texture_or_null` when absence is + /// meaningful. pub fn get_spectrum_texture( &self, name: &str, - val: Option, + def: impl Into, stype: SpectrumType, - ) -> Option> { - let tex = self.get_spectrum_texture_or_null(name, stype); - if tex.is_some() { - tex - } else if val.is_some() { - Some(Arc::new(SpectrumTexture::Constant( - SpectrumConstantTexture::new(val.unwrap()), - ))) - } else { - None - } + ) -> Arc { + self.get_spectrum_texture_or_null(name, stype) + .unwrap_or_else(|| Arc::new(SpectrumTexture::from(def.into()))) } pub fn get_float_texture(&self, name: &str, val: Float) -> Result> {