Bunch of logic errors in port of math and sampling functions

This commit is contained in:
Wito Wiala 2026-09-02 17:21:16 +01:00
parent 28bb963268
commit 34ea80c030
17 changed files with 162 additions and 123 deletions

View file

@ -251,7 +251,7 @@ impl TabulatedBSSRDF {
sr += weight sr += weight
* self * self
.table .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() { 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 += 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' // 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 sr += self
.table .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 * rho_weight
* radius_weight; * radius_weight;
} }

View file

@ -625,7 +625,8 @@ impl RGBSigmoidPolynomial {
} }
pub fn evaluate(&self, lambda: Float) -> Float { 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) Self::s(eval)
} }

View file

@ -192,15 +192,18 @@ impl SamplerTrait for HaltonSampler {
} }
fn get1d(&mut self) -> Float { 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; self.dim = 2;
} }
let dim = self.dim;
self.dim += 1; self.dim += 1;
self.sample_dimension(self.dim) self.sample_dimension(dim)
} }
fn get2d(&mut self) -> Point2f { 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; self.dim = 2;
} }
let dim = self.dim; let dim = self.dim;
@ -301,8 +304,9 @@ impl SamplerTrait for StratifiedSampler {
hash as u32, hash as u32,
); );
self.dim += 2; self.dim += 2;
// pbrt: both the modulus and the divisor are xPixelSamples.
let x = stratum % self.x_pixel_samples as u32; 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 { let dx = if self.jitter {
self.rng.uniform::<Float>() self.rng.uniform::<Float>()
} else { } else {
@ -387,6 +391,7 @@ impl SamplerTrait for PaddedSobolSampler {
self.samples_per_pixel as u32, self.samples_per_pixel as u32,
hash as u32, hash as u32,
); );
self.dim += 1;
self.sample_dimension(0, index, (hash >> 32) as u32) self.sample_dimension(0, index, (hash >> 32) as u32)
} }
fn get2d(&mut self) -> Point2f { fn get2d(&mut self) -> Point2f {
@ -512,7 +517,7 @@ impl SamplerTrait for SobolSampler {
) as Float; ) as Float;
u[1] = clamp( u[1] = clamp(
u[1] * self.scale as Float - self.pixel[1] as Float, u[1] * self.scale as Float - self.pixel[1] as Float,
1., 0.,
ONE_MINUS_EPSILON, ONE_MINUS_EPSILON,
) as Float; ) as Float;
u u

View file

@ -1,8 +1,8 @@
use crate::core::geometry::{spherical_triangle_area, SqrtExt, Tuple, VectorLike};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
Vector3f, Vector3f,
}; };
use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction, Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
}; };
@ -13,7 +13,7 @@ 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 crate::{gamma, Float, GVec, Ptr}; use crate::{Float, GVec, Ptr, gamma};
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -198,34 +198,34 @@ impl TriangleShape {
// Ensure that computed triangle $t$ is conservatively greater than zero // Ensure that computed triangle $t$ is conservatively greater than zero
// Compute $\delta_z$ term for triangle $t$ error bounds // 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() .abs()
.max_component_value(); .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 // 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() .abs()
.max_component_value(); .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() .abs()
.max_component_value(); .max_component_value();
let deltaX = gamma(5) * (maxXt + maxZt); let delta_x = gamma(5) * (max_xt + max_zt);
let deltaY = gamma(5) * (maxYt + maxZt); let delta_y = gamma(5) * (max_yt + max_zt);
// Compute $\delta_e$ term for triangle $t$ error bounds // 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_ // Compute $\delta_t$ term for triangle $t$ error bounds and check _t_
let maxE = Vector3f::new(e0, e1, e2).abs().max_component_value(); let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value();
let deltaT = let delta_t =
3. * (gamma(3) * maxE * maxZt + deltaE * maxZt + deltaZ * maxE) * inv_det.abs(); 3. * (gamma(3) * max_e * max_zt + delta_e * max_zt + delta_z * max_e) * inv_det.abs();
if t <= deltaT { if t <= delta_t {
return None; return None;
} }
// Return _TriangleIntersection_ for intersection // Return _TriangleIntersection_ for intersection
return Some(TriangleIntersection { b0, b1, b2, t }); Some(TriangleIntersection { b0, b1, b2, t })
} }
fn interaction_from_intersection( fn interaction_from_intersection(
@ -345,11 +345,11 @@ impl TriangleShape {
let mut ts = ns.cross(ss.into()); let mut ts = ns.cross(ss.into());
if ts.norm_squared() > 0.0 { if ts.norm_squared() > 0.0 {
ss = ts.cross(ns.into()).into(); ss = ts.cross(ns).into();
} else { } else {
let (s, t) = ns.coordinate_system(); let (s, t) = ns.coordinate_system();
ss = s.into(); ss = s.into();
ts = t.into(); ts = t;
} }
let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() { 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<Float>) -> Option<ShapeIntersection> { fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let [p0, p1, p2] = self.get_points(); 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); let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d);
Some(ShapeIntersection::new(intr, tri_isect.t)) Some(ShapeIntersection::new(intr, tri_isect.t))
} }

View file

@ -61,11 +61,14 @@ where
T::lerp(t, a, b) 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] #[inline]
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float { pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
assert!(!coeffs.is_empty()); assert!(!coeffs.is_empty());
let mut result = coeffs[0]; let mut result = coeffs[coeffs.len() - 1];
for &c in &coeffs[1..] { for &c in coeffs[..coeffs.len() - 1].iter().rev() {
result = num_traits::Float::mul_add(t, result, c); result = num_traits::Float::mul_add(t, result, c);
} }
result result
@ -96,7 +99,7 @@ where
#[inline] #[inline]
pub fn safe_sqrt(x: Float) -> Float { pub fn safe_sqrt(x: Float) -> Float {
assert!(x > -1e-3); debug_assert!(x > -1e-3);
0.0_f32.max(x).sqrt() 0.0_f32.max(x).sqrt()
} }
@ -104,20 +107,14 @@ pub fn safe_sqrt(x: Float) -> Float {
pub fn safe_asin<T: NumFloat>(x: T) -> T { pub fn safe_asin<T: NumFloat>(x: T) -> T {
let epsilon = T::from(0.0001).unwrap(); let epsilon = T::from(0.0001).unwrap();
let one = T::one(); let one = T::one();
if x >= -(one + epsilon) && x <= one + epsilon { debug_assert!(x >= -(one + epsilon) && x <= one + epsilon);
clamp(x, -one, one).asin() clamp(x, -one, one).asin()
} else {
panic!("Not valid value for asin")
}
} }
#[inline] #[inline]
pub fn safe_acos(x: Float) -> Float { pub fn safe_acos(x: Float) -> Float {
if (-1.001..1.001).contains(&x) { debug_assert!((-1.001..1.001).contains(&x));
clamp(x, -1., 1.).acos() clamp(x, -1., 1.).acos()
} else {
panic!("Not valid value for acos")
}
} }
#[inline] #[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 { pub fn integrate_catmull_rom(nodes: &[Float], f: &[Float], cdf: &mut [Float]) -> Float {
debug_assert_eq!(nodes.len(), f.len()); debug_assert_eq!(nodes.len(), f.len());
debug_assert_eq!(cdf.len(), nodes.len());
let mut sum = 0.; let mut sum = 0.;
cdf[0] = 0.; cdf[0] = 0.;
for i in 0..nodes.len() { for i in 0..nodes.len() - 1 {
let x0 = nodes[i]; let x0 = nodes[i];
let x1 = nodes[i + 1]; let x1 = nodes[i + 1];
let f0 = f[i]; let f0 = f[i];
@ -434,7 +432,7 @@ pub fn invert_catmull_rom(nodes: &[Float], f: &[Float], u: Float) -> Float {
return x0 + t * width; 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 { if nodes.len() < 4 {
return None; 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) // Safety clamp (though bounds check above handles most cases)
let idx = idx.min(nodes.len() - 2); 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 x0 = nodes[idx];
let x1 = nodes[idx + 1]; 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; weights[3] = 0.0;
} }
Some((offset as u32, weights)) Some((offset, weights))
} }
pub fn equal_area_sphere_to_square(d: Vector3f) -> Point2f { 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 // Compute angle \phi for square to sphere mapping
let mut phi = if r == 0. { 1. } else { (vp - up) / r + 1. }; 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 // Find z for spherical direction
let z = (1. - square(r)).copysign(signed_distance); 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)) 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 { pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
assert!(a >= 0. && b >= 0.); assert!(a >= 0. && b >= 0.);
if u == 0. && a == 0. { if u == 0. && a == 0. {
return 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) x.min(ONE_MINUS_EPSILON)
} }
@ -700,12 +702,13 @@ pub fn sample_discrete(
} }
pub fn sample_tent(u: Float, r: Float) -> Float { 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 mut u_remapped = 0.0;
let offset = sample_discrete(&[0.5, 0.5], u, None, Some(&mut u_remapped)); let offset = sample_discrete(&[0.5, 0.5], u, None, Some(&mut u_remapped));
if offset == 0 { if offset == 0 {
-r + r * sample_linear(u, 0., 1.) -r + r * sample_linear(u_remapped, 0., 1.)
} else { } 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 { 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 { 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(); 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; m <<= 1;
e += 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 { } else if exp == 0x1F {
(sign << 31) | (0xFF << 23) | (mant << 13) (sign << 31) | (0xFF << 23) | (mant << 13)

View file

@ -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 { pub fn sample_bilinear(u: Point2f, w: &[Float]) -> Point2f {
let y = sample_linear(u[1], w[0] + w[1], w[2] + w[3]); 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) Point2f::new(x, y)
} }
@ -333,7 +333,8 @@ pub fn invert_spherical_rectangle_sample(
} }
pub fn sample_exponential(u: Float, a: Float) -> Float { 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( pub fn sample_spherical_triangle(
@ -550,7 +551,9 @@ pub fn sample_catmull_rom_2d(
let mut v = 0.; let mut v = 0.;
for i in 0..4 { for i in 0..4 {
if weights[i] != 0. { 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]; v += array[ind as usize] * weights[i];
} }
} }
@ -658,10 +661,10 @@ pub struct VarianceEstimator {
impl VarianceEstimator { impl VarianceEstimator {
pub fn add(&mut self, x: Float) { pub fn add(&mut self, x: Float) {
self.n += 1; self.n += 1;
let delta = x / self.mean; let delta = x - self.mean;
self.mean += delta / self.n as Float; self.mean += delta / self.n as Float;
let delta2 = x - self.mean; let delta2 = x - self.mean;
self.s *= delta * delta2; self.s += delta * delta2;
} }
pub fn mean(&self) -> Float { pub fn mean(&self) -> Float {
@ -688,7 +691,7 @@ impl VarianceEstimator {
if ve.n != 0 { if ve.n != 0 {
self.s += ve.s self.s += ve.s
+ square(ve.mean - self.mean) * (self.n * ve.n) as Float / (self.n + ve.n) as Float; + 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 as Float * self.mean + ve.n as Float * ve.mean) / (self.n + ve.n) as Float;
self.n += ve.n; self.n += ve.n;
} }
@ -719,24 +722,37 @@ impl PiecewiseConstant1D {
} }
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self { pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
debug_assert!(max > min);
let n = f.len(); 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<Float> = gvec_with_capacity(n);
for &v in f {
func.push(v.abs());
}
let mut cdf = gvec_with_capacity(n + 1); let mut cdf = gvec_with_capacity(n + 1);
cdf.push(0.0); cdf.push(0.0);
let delta = (max - min) / n as Float; let delta = (max - min) / n as Float;
for i in 0..n { for i in 0..n {
cdf.push(cdf[i] + f[i] * delta); cdf.push(cdf[i] + func[i] * delta);
} }
let func_integral = cdf[n]; let func_integral = cdf[n];
if func_integral > 0.0 { if func_integral == 0.0 {
for c in &mut cdf { 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; *c /= func_integral;
} }
} }
Self { Self {
func: gvec_from_slice(f), func,
cdf, cdf,
min, min,
max, max,
@ -748,7 +764,6 @@ impl PiecewiseConstant1D {
where where
F: Fn(Float) -> Float, F: Fn(Float) -> Float,
{ {
let delta = (max - min) / n as Float;
let delta = (max - min) / n as Float; let delta = (max - min) / n as Float;
let mut values = gvec_with_capacity(n); let mut values = gvec_with_capacity(n);
for i in 0..n { for i in 0..n {
@ -816,6 +831,7 @@ impl PiecewiseConstant1D {
pub struct PiecewiseConstant2D { pub struct PiecewiseConstant2D {
pub conditionals: GVec<PiecewiseConstant1D>, pub conditionals: GVec<PiecewiseConstant1D>,
pub marginal: PiecewiseConstant1D, pub marginal: PiecewiseConstant1D,
pub domain: Bounds2f,
pub n_u: u32, pub n_u: u32,
pub n_v: u32, pub n_v: u32,
} }
@ -852,6 +868,7 @@ impl PiecewiseConstant2D {
Self { Self {
conditionals, conditionals,
marginal, marginal,
domain,
n_u: n_u.try_into().unwrap(), n_u: n_u.try_into().unwrap(),
n_v: n_v.try_into().unwrap(), n_v: n_v.try_into().unwrap(),
} }
@ -876,7 +893,10 @@ impl PiecewiseConstant2D {
self.marginal.integral() 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 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 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) }; let conditional = unsafe { &*self.conditionals.as_ptr().add(v_offset) };

View file

@ -81,16 +81,15 @@ impl MaterialFactory for Material {
SubsurfaceMaterial::create(parameters, normal_map, named_materials, &loc, arena) SubsurfaceMaterial::create(parameters, normal_map, named_materials, &loc, arena)
} }
"mix" => MixMaterial::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)), _ => Err(anyhow!("Material type '{}' unknown at {}", name, &loc)),
} }
} }
} }
pub fn default_diffuse_material(arena: &Arena) -> Material { 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::spectrum::{ConstantSpectrum, Spectrum};
use shared::core::texture::SpectrumConstantTexture;
use shared::core::texture::SpectrumTexture;
use shared::materials::DiffuseMaterial; use shared::materials::DiffuseMaterial;
use shared::utils::Ptr; use shared::utils::Ptr;

View file

@ -24,10 +24,7 @@ impl CreateMaterial for CoatedDiffuseMaterial {
arena: &Arena, arena: &Arena,
) -> Result<Material> { ) -> Result<Material> {
let reflectance = parameters let reflectance = parameters
.get_spectrum_texture("reflectance", None, SpectrumType::Albedo) .get_spectrum_texture("reflectance", 0.5, SpectrumType::Albedo);
.unwrap_or(Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))),
)));
let u_roughness = let u_roughness =
parameters.get_float_texture_with_fallback("uroughness", "roughness", 0.5)?; 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 n_samples = parameters.get_one_int("nsamples", 1)?;
let g = parameters.get_float_texture("g", 0.)?; let g = parameters.get_float_texture("g", 0.)?;
let albedo = parameters let albedo = parameters
.get_spectrum_texture("albedo", None, SpectrumType::Albedo) .get_spectrum_texture("albedo", 0., SpectrumType::Albedo);
.unwrap_or_else(|| {
let default_spectrum = Spectrum::Constant(ConstantSpectrum::new(0.));
SpectrumTexture::Constant(SpectrumConstantTexture::new(default_spectrum)).into()
});
let displacement = parameters.get_float_texture("displacement", 0.)?; let displacement = parameters.get_float_texture("displacement", 0.)?;
let remap_roughness = parameters.get_one_bool("remaproughness", true)?; 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 n_samples = parameters.get_one_int("nsamples", 1)?;
let g = parameters.get_float_texture("g", 0.)?; let g = parameters.get_float_texture("g", 0.)?;
let albedo = parameters let albedo = parameters
.get_spectrum_texture("albedo", None, SpectrumType::Albedo) .get_spectrum_texture("albedo", 0., SpectrumType::Albedo);
.unwrap_or_else(|| {
let spectrum = Spectrum::Constant(ConstantSpectrum::new(0.));
SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)).into()
});
let displacement = parameters.get_float_texture_or_null("displacement")?; let displacement = parameters.get_float_texture_or_null("displacement")?;
let remap_roughness = parameters.get_one_bool("remaproughness", true)?; let remap_roughness = parameters.get_one_bool("remaproughness", true)?;

View file

@ -240,8 +240,7 @@ impl CreateMaterial for SubsurfaceMaterial {
Some(r) => { Some(r) => {
let one = Spectrum::Constant(ConstantSpectrum::new(1.)); let one = Spectrum::Constant(ConstantSpectrum::new(1.));
let mfp = parameters let mfp = parameters
.get_spectrum_texture("mfp", Some(one), SpectrumType::Unbounded) .get_spectrum_texture("mfp", one, SpectrumType::Unbounded);
.expect("default supplied");
SubsurfaceScattering::Reflectance { SubsurfaceScattering::Reflectance {
reflectance: arena.upload(r), reflectance: arena.upload(r),
mfp: arena.upload(mfp), mfp: arena.upload(mfp),

View file

@ -52,7 +52,7 @@ impl CreateMaterial for ThinDielectricMaterial {
parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
normal_map: Option<Arc<HostImage>>, normal_map: Option<Arc<HostImage>>,
_named_materials: &HashMap<String, Material>, _named_materials: &HashMap<String, Material>,
loc: &FileLoc, _loc: &FileLoc,
arena: &Arena, arena: &Arena,
) -> Result<Material> { ) -> Result<Material> {
let eta = if let Some(&v) = parameters.get_float_array("eta")?.first() { let eta = if let Some(&v) = parameters.get_float_array("eta")?.first() {

View file

@ -1,9 +1,9 @@
use crate::Arena;
use crate::core::image::HostImage; use crate::core::image::HostImage;
use crate::core::material::CreateMaterial; use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture; use crate::core::texture::SpectrumTexture;
use crate::utils::upload::ArenaUpload; use crate::utils::upload::ArenaUpload;
use crate::utils::{FileLoc, TextureParameterDictionary}; use crate::utils::{FileLoc, TextureParameterDictionary};
use crate::Arena;
use anyhow::Result; use anyhow::Result;
use shared::core::material::Material; use shared::core::material::Material;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
@ -22,13 +22,7 @@ impl CreateMaterial for DiffuseMaterial {
_loc: &FileLoc, _loc: &FileLoc,
arena: &Arena, arena: &Arena,
) -> Result<Material> { ) -> Result<Material> {
let reflectance = parameters let reflectance = parameters.get_spectrum_texture("reflectance", 0.5, SpectrumType::Albedo);
.get_spectrum_texture("reflectance", None, SpectrumType::Albedo)
.unwrap_or_else(|| {
Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))),
))
});
let displacement = parameters.get_float_texture_or_null("displacement")?; let displacement = parameters.get_float_texture_or_null("displacement")?;
let specific = DiffuseMaterial { let specific = DiffuseMaterial {
@ -42,12 +36,26 @@ impl CreateMaterial for DiffuseMaterial {
impl CreateMaterial for DiffuseTransmissionMaterial { impl CreateMaterial for DiffuseTransmissionMaterial {
fn create( fn create(
_parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
_normal_map: Option<Arc<HostImage>>, normal_map: Option<Arc<HostImage>>,
_named_materials: &HashMap<String, Material>, _named_materials: &HashMap<String, Material>,
_loc: &FileLoc, _loc: &FileLoc,
_arena: &Arena, arena: &Arena,
) -> Result<Material> { ) -> Result<Material> {
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))
} }
} }

View file

@ -1,7 +1,9 @@
use crate::core::image::HostImage; use crate::core::image::HostImage;
use crate::core::material::CreateMaterial; use crate::core::material::CreateMaterial;
use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; use crate::{Arena, ArenaUpload, FileLoc, utils::TextureParameterDictionary};
use anyhow::Result; use anyhow::Result;
use anyhow::anyhow;
use shared::Ptr;
use shared::core::material::Material; use shared::core::material::Material;
use shared::materials::MixMaterial; use shared::materials::MixMaterial;
use std::collections::HashMap; use std::collections::HashMap;
@ -9,12 +11,31 @@ use std::sync::Arc;
impl CreateMaterial for MixMaterial { impl CreateMaterial for MixMaterial {
fn create( fn create(
_parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
_normal_map: Option<Arc<HostImage>>, _normal_map: Option<Arc<HostImage>>,
_named_materials: &HashMap<String, Material>, named_materials: &HashMap<String, Material>,
_loc: &FileLoc, loc: &FileLoc,
_arena: &Arena, arena: &Arena,
) -> Result<Material> { ) -> Result<Material> {
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<Ptr<Material>> {
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))
} }
} }

View file

@ -64,8 +64,7 @@ impl CreateSpectrumTexture for SpectrumCheckerboardTexture {
let tex = |name: &str, def: Spectrum| { let tex = |name: &str, def: Spectrum| {
arena.upload( arena.upload(
parameters parameters
.get_spectrum_texture(name, Some(def), spectrum_type) .get_spectrum_texture(name, def, spectrum_type),
.expect("default supplied"),
) )
}; };

View file

@ -47,8 +47,7 @@ impl CreateSpectrumTexture for SpectrumDotsTexture {
let get = |name: &str, def: Spectrum| { let get = |name: &str, def: Spectrum| {
let t = parameters let t = parameters
.get_spectrum_texture(name, Some(def), spectrum_type) .get_spectrum_texture(name, def, spectrum_type);
.expect("default supplied");
arena.upload(t) arena.upload(t)
}; };
let tex = SpectrumDotsTexture::new(map, get("inside", one), get("outside", zero)); let tex = SpectrumDotsTexture::new(map, get("inside", one), get("outside", zero));

View file

@ -87,8 +87,7 @@ impl CreateSpectrumTexture for SpectrumMixTexture {
let tex = |name: &str, def: Spectrum| { let tex = |name: &str, def: Spectrum| {
parameters parameters
.get_spectrum_texture(name, Some(def), spectrum_type) .get_spectrum_texture(name, def, spectrum_type)
.expect("default supplied")
}; };
let tex = SpectrumMixTexture { let tex = SpectrumMixTexture {
@ -119,8 +118,7 @@ impl CreateSpectrumTexture for SpectrumDirectionMixTexture {
let dir = render_from_texture.apply_to_vector(dir_raw).normalize(); let dir = render_from_texture.apply_to_vector(dir_raw).normalize();
let tex = |name: &str, def: Spectrum| { let tex = |name: &str, def: Spectrum| {
parameters parameters
.get_spectrum_texture(name, Some(def), spectrum_type) .get_spectrum_texture(name, def, spectrum_type)
.expect("default supplied")
}; };
let zero = Spectrum::Constant(ConstantSpectrum::new(0.)); let zero = Spectrum::Constant(ConstantSpectrum::new(0.));

View file

@ -66,9 +66,8 @@ impl CreateSpectrumTexture for SpectrumScaledTexture {
_arena: &Arena, _arena: &Arena,
) -> Result<SpectrumTexture> { ) -> Result<SpectrumTexture> {
let one = Spectrum::Constant(ConstantSpectrum::new(1.0)); let one = Spectrum::Constant(ConstantSpectrum::new(1.0));
let tex = parameters // pbrt defaults "tex" to 1.0 rather than erroring (textures.cpp).
.get_spectrum_texture("tex", Some(one), spectrum_type) let tex = parameters.get_spectrum_texture("tex", one, spectrum_type);
.ok_or_else(|| anyhow::anyhow!("{}: missing \"tex\" parameter", _loc))?;
let scale = parameters.get_float_texture("scale", 1.0)?; let scale = parameters.get_float_texture("scale", 1.0)?;
if let FloatTexture::Constant(ref cscale) = *scale { if let FloatTexture::Constant(ref cscale) = *scale {

View file

@ -835,22 +835,18 @@ impl TextureParameterDictionary {
self.dict.report_unused() 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( pub fn get_spectrum_texture(
&self, &self,
name: &str, name: &str,
val: Option<Spectrum>, def: impl Into<Spectrum>,
stype: SpectrumType, stype: SpectrumType,
) -> Option<Arc<SpectrumTexture>> { ) -> Arc<SpectrumTexture> {
let tex = self.get_spectrum_texture_or_null(name, stype); self.get_spectrum_texture_or_null(name, stype)
if tex.is_some() { .unwrap_or_else(|| Arc::new(SpectrumTexture::from(def.into())))
tex
} else if val.is_some() {
Some(Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(val.unwrap()),
)))
} else {
None
}
} }
pub fn get_float_texture(&self, name: &str, val: Float) -> Result<Arc<FloatTexture>> { pub fn get_float_texture(&self, name: &str, val: Float) -> Result<Arc<FloatTexture>> {