Big slog, separating GPU and CPU safe structs and constructors

This commit is contained in:
pingu 2025-12-21 02:17:28 +00:00
parent 2e9d3c7301
commit cda63e42c5
52 changed files with 2253 additions and 1464 deletions

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View file

@ -14,6 +14,8 @@ num-integer = "0.1.46"
num-traits = "0.2.19" 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 }
[features] [features]
cuda = [] use_f64 = []
cuda = ["cuda_std"]

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,7 @@
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use crate::utils::error::FileLoc;
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc}; use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
use crate::utils::parameters::ParameterDictionary;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
@ -19,6 +17,7 @@ pub struct FilterSampler {
f: Array2D<Float>, f: Array2D<Float>,
} }
#[cfg(not(target_os = "cuda"))]
impl FilterSampler { impl FilterSampler {
pub fn new<F>(radius: Vector2f, func: F) -> Self pub fn new<F>(radius: Vector2f, func: F) -> Self
where where
@ -45,27 +44,21 @@ impl FilterSampler {
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain); let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
Self { domain, f, distrib } Self { domain, f, distrib }
} }
}
/// Samples the filter's distribution. impl FilterSampler {
pub fn sample(&self, u: Point2f) -> FilterSample { pub fn sample(&self, u: Point2f) -> FilterSample {
let (p, pdf, pi) = self.distrib.sample(u); let (p, pdf, pi) = self.distrib.sample(u);
if pdf == 0.0 { if pdf == 0.0 {
return FilterSample { p, weight: 0.0 }; return FilterSample { p, weight: 0.0 };
} }
let weight = self.f[pi] / pdf;
let weight = *self.f.get_linear(pi.x() as usize + self.f.x_size()) / pdf;
FilterSample { p, weight } FilterSample { p, weight }
} }
} }
#[enum_dispatch]
pub trait FilterTrait {
fn radius(&self) -> Vector2f;
fn evaluate(&self, p: Point2f) -> Float;
fn integral(&self) -> Float;
fn sample(&self, u: Point2f) -> FilterSample;
}
#[enum_dispatch(FilterTrait)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Filter { pub enum Filter {
Box(BoxFilter), Box(BoxFilter),
@ -76,310 +69,42 @@ pub enum Filter {
} }
impl Filter { impl Filter {
pub fn create(name: &str, params: ParameterDictionary, loc: &FileLoc) -> Result<Self, String> { pub fn radius(&self) -> Vector2f {
match name { match self {
"box" => { Filter::Box(f) => f.radius(),
let filter = BoxFilter::create(&params, loc); Filter::Gaussian(f) => f.radius(),
Ok(Filter::Box(filter)) Filter::Mitchell(f) => f.radius(),
} Filter::LanczosSinc(f) => f.radius(),
"gaussian" => { Filter::Triangle(f) => f.radius(),
let filter = GaussianFilter::create(&params, loc);
Ok(Filter::Gaussian(filter))
}
"mitchell" => {
let filter = MitchellFilter::create(&params, loc);
Ok(Filter::Mitchell(filter))
}
"sinc" => {
let filter = LanczosSincFilter::create(&params, loc);
Ok(Filter::LanczosSinc(filter))
}
"triangle" => {
let filter = TriangleFilter::create(&params, loc);
Ok(Filter::Triangle(filter))
}
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
}
}
}
#[derive(Clone, Debug)]
pub struct BoxFilter {
pub radius: Vector2f,
}
impl BoxFilter {
pub fn new(radius: Vector2f) -> Self {
Self { radius }
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 0.5);
let yw = params.get_one_float("yradius", 0.5);
Self::new(Vector2f::new(xw, yw))
}
}
impl FilterTrait for BoxFilter {
fn radius(&self) -> Vector2f {
self.radius
}
fn evaluate(&self, p: Point2f) -> Float {
if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() {
1.
} else {
0.
} }
} }
fn integral(&self) -> Float { pub fn evaluate(&self, p: Point2f) -> Float {
(2.0 * self.radius.x()) * (2.0 * self.radius.y()) match self {
} Filter::Box(f) => f.evaluate(p),
Filter::Gaussian(f) => f.evaluate(p),
fn sample(&self, u: Point2f) -> FilterSample { Filter::Mitchell(f) => f.evaluate(p),
let p = Point2f::new( Filter::LanczosSinc(f) => f.evaluate(p),
lerp(u[0], -self.radius.x(), self.radius.x()), Filter::Triangle(f) => f.evaluate(p),
lerp(u[1], -self.radius.y(), self.radius.y()),
);
FilterSample { p, weight: 1.0 }
}
}
#[derive(Clone, Debug)]
pub struct GaussianFilter {
pub radius: Vector2f,
pub sigma: Float,
pub exp_x: Float,
pub exp_y: Float,
pub sampler: FilterSampler,
}
impl GaussianFilter {
pub fn new(radius: Vector2f, sigma: Float) -> Self {
let exp_x = gaussian(radius.x(), 0., sigma);
let exp_y = gaussian(radius.y(), 0., sigma);
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let gx = (gaussian(p.x(), 0., sigma) - exp_x).max(0.0);
let gy = (gaussian(p.y(), 0., sigma) - exp_y).max(0.0);
gx * gy
});
Self {
radius,
sigma,
exp_x: gaussian(radius.x(), 0., sigma),
exp_y: gaussian(radius.y(), 0., sigma),
sampler,
} }
} }
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self { pub fn integral(&self) -> Float {
let xw = params.get_one_float("xradius", 1.5); match self {
let yw = params.get_one_float("yradius", 1.5); Filter::Box(f) => f.integral(),
let sigma = params.get_one_float("sigma", 0.5); Filter::Gaussian(f) => f.integral(),
Self::new(Vector2f::new(xw, yw), sigma) Filter::Mitchell(f) => f.integral(),
} Filter::LanczosSinc(f) => f.integral(),
} Filter::Triangle(f) => f.integral(),
impl FilterTrait for GaussianFilter {
fn radius(&self) -> Vector2f {
self.radius
}
fn evaluate(&self, p: Point2f) -> Float {
(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)
}
fn integral(&self) -> Float {
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
- 2.0 * self.radius.x() * self.exp_x)
* (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma)
- 2.0 * self.radius.y() * self.exp_y)
}
fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u)
}
}
#[derive(Clone, Debug)]
pub struct MitchellFilter {
pub radius: Vector2f,
pub b: Float,
pub c: Float,
pub sampler: FilterSampler,
}
impl MitchellFilter {
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let nx = 2.0 * p.x() / radius.x();
let ny = 2.0 * p.y() / radius.y();
Self::mitchell_1d_eval(b, c, nx) * Self::mitchell_1d_eval(b, c, ny)
});
Self {
radius,
b,
c,
sampler,
} }
} }
pub fn sample(&self, u: Point2f) -> FilterSample {
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self { match self {
let xw = params.get_one_float("xradius", 2.); Filter::Box(f) => f.sample(u),
let yw = params.get_one_float("yradius", 2.); Filter::Gaussian(f) => f.sample(u),
let b = params.get_one_float("B", 1. / 3.); Filter::Mitchell(f) => f.sample(u),
let c = params.get_one_float("C", 1. / 3.); Filter::LanczosSinc(f) => f.sample(u),
Self::new(Vector2f::new(xw, yw), b, c) Filter::Triangle(f) => f.sample(u),
}
fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
let x = x.abs();
if x <= 1.0 {
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
+ (6.0 - 2.0 * b))
* (1.0 / 6.0)
} else if x <= 2.0 {
((-b - 6.0 * c) * x.powi(3)
+ (6.0 * b + 30.0 * c) * x.powi(2)
+ (-12.0 * b - 48.0 * c) * x
+ (8.0 * b + 24.0 * c))
* (1.0 / 6.0)
} else {
0.0
} }
} }
fn mitchell_1d(&self, x: Float) -> Float {
Self::mitchell_1d_eval(self.b, self.c, x)
}
}
impl FilterTrait for MitchellFilter {
fn radius(&self) -> Vector2f {
self.radius
}
fn evaluate(&self, p: Point2f) -> Float {
self.mitchell_1d(2.0 * p.x() / self.radius.x())
* self.mitchell_1d(2.0 * p.y() / self.radius.y())
}
fn integral(&self) -> Float {
self.radius.x() * self.radius.y() / 4.0
}
fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u)
}
}
#[derive(Clone, Debug)]
pub struct LanczosSincFilter {
pub radius: Vector2f,
pub tau: Float,
pub sampler: FilterSampler,
}
impl LanczosSincFilter {
pub fn new(radius: Vector2f, tau: Float) -> Self {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
});
Self {
radius,
tau,
sampler,
}
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 4.);
let yw = params.get_one_float("yradius", 4.);
let tau = params.get_one_float("tau", 3.);
Self::new(Vector2f::new(xw, yw), tau)
}
}
impl FilterTrait for LanczosSincFilter {
fn radius(&self) -> Vector2f {
self.radius
}
fn evaluate(&self, p: Point2f) -> Float {
windowed_sinc(p.x(), self.radius.x(), self.tau)
* windowed_sinc(p.y(), self.radius.y(), self.tau)
}
fn integral(&self) -> Float {
let sqrt_samples = 64;
let n_samples = sqrt_samples * sqrt_samples;
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
let mut sum = 0.0;
let mut rng = rand::rng();
for y in 0..sqrt_samples {
for x in 0..sqrt_samples {
let u = Point2f::new(
(x as Float + rng.random::<Float>()) / sqrt_samples as Float,
(y as Float + rng.random::<Float>()) / sqrt_samples as Float,
);
let p = Point2f::new(
lerp(u.x(), -self.radius.x(), self.radius.x()),
lerp(u.y(), -self.radius.y(), self.radius.y()),
);
sum += self.evaluate(p);
}
}
sum / n_samples as Float * area
}
fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u)
}
}
#[derive(Clone, Debug)]
pub struct TriangleFilter {
pub radius: Vector2f,
}
impl TriangleFilter {
pub fn new(radius: Vector2f) -> Self {
Self { radius }
}
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
let xw = params.get_one_float("xradius", 2.);
let yw = params.get_one_float("yradius", 2.);
Self::new(Vector2f::new(xw, yw))
}
}
impl FilterTrait for TriangleFilter {
fn radius(&self) -> Vector2f {
self.radius
}
fn evaluate(&self, p: Point2f) -> Float {
(self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0)
}
fn integral(&self) -> Float {
self.radius.x().powi(2) * self.radius.y().powi(2)
}
fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new(
sample_tent(u[0], self.radius.x()),
sample_tent(u[1], self.radius.y()),
);
FilterSample { p, weight: 1.0 }
}
} }

View file

@ -10,7 +10,7 @@ use crate::core::pbrt::{Float, PI};
use crate::images::Image; use crate::images::Image;
use crate::spectra::{ use crate::spectra::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum,
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
}; };
use crate::utils::containers::InternCache; use crate::utils::containers::InternCache;
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};

View file

@ -16,7 +16,7 @@ use crate::core::texture::{
FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator, FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator,
}; };
use crate::image::{Image, WrapMode, WrapMode2D}; use crate::image::{Image, WrapMode, WrapMode2D};
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider}; use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait};
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::math::clamp; use crate::utils::math::clamp;

View file

@ -8,7 +8,7 @@ use crate::core::geometry::{
use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::core::pbrt::{Float, INV_4_PI, PI};
use crate::spectra::{ use crate::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
}; };
use crate::utils::containers::SampledGrid; use crate::utils::containers::SampledGrid;
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};

View file

@ -8,29 +8,22 @@ use crate::images::WrapMode;
use crate::spectra::color::ColorEncoding; use crate::spectra::color::ColorEncoding;
use crate::spectra::{ use crate::spectra::{
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, Spectrum, SpectrumProvider, SampledWavelengths, Spectrum, SpectrumTrait,
}; };
use crate::textures::*;
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::utils::transform::TransformGeneric;
use enum_dispatch::enum_dispatch; #[repr(C)]
use std::path::Path; pub struct TexCoord2D {
pub st: Point2f,
struct TexCoord2D { pub dsdx: Float,
st: Point2f, pub dsdy: Float,
dsdx: Float, pub dtdx: Float,
dsdy: Float, pub dtdy: Float,
dtdx: Float,
dtdy: Float,
} }
#[enum_dispatch] #[repr(C)]
trait TextureMapping2DTrait {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D;
}
#[enum_dispatch(TextureMapping2DTrait)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TextureMapping2D { pub enum TextureMapping2D {
UV(UVMapping), UV(UVMapping),
@ -39,6 +32,17 @@ pub enum TextureMapping2D {
Planar(PlanarMapping), Planar(PlanarMapping),
} }
impl TextureMapping2D {
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
match self {
TextureMapping2D::UV(t) => t.map(ctx),
TextureMapping2D::Spherical(t) => t.map(ctx),
TextureMapping2D::Cylindrical(t) => t.map(ctx),
TextureMapping2D::Planar(t) => t.map(ctx),
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct UVMapping { pub struct UVMapping {
su: Float, su: Float,
@ -47,12 +51,6 @@ pub struct UVMapping {
dv: Float, dv: Float,
} }
impl UVMapping {
pub fn new(su: Float, sv: Float, du: Float, dv: Float) -> Self {
Self { su, sv, du, dv }
}
}
impl Default for UVMapping { impl Default for UVMapping {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -64,8 +62,12 @@ impl Default for UVMapping {
} }
} }
impl TextureMapping2DTrait for UVMapping { impl UVMapping {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D { pub fn new(su: Float, sv: Float, du: Float, dv: Float) -> Self {
Self { su, sv, du, dv }
}
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let dsdx = self.su * ctx.dudx; let dsdx = self.su * ctx.dudx;
let dsdy = self.su * ctx.dudy; let dsdy = self.su * ctx.dudy;
let dtdx = self.sv * ctx.dvdx; let dtdx = self.sv * ctx.dvdx;
@ -83,19 +85,17 @@ impl TextureMapping2DTrait for UVMapping {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SphericalMapping { pub struct SphericalMapping {
texture_from_render: TransformGeneric<Float>, texture_from_render: Transform,
} }
impl SphericalMapping { impl SphericalMapping {
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self { pub fn new(texture_from_render: &Transform) -> Self {
Self { Self {
texture_from_render: *texture_from_render, texture_from_render: *texture_from_render,
} }
} }
}
impl TextureMapping2DTrait for SphericalMapping { pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let pt = self.texture_from_render.apply_to_point(ctx.p); let pt = self.texture_from_render.apply_to_point(ctx.p);
let x2y2 = square(pt.x()) + square(pt.y()); let x2y2 = square(pt.x()) + square(pt.y());
let sqrtx2y2 = x2y2.sqrt(); let sqrtx2y2 = x2y2.sqrt();
@ -126,19 +126,17 @@ impl TextureMapping2DTrait for SphericalMapping {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CylindricalMapping { pub struct CylindricalMapping {
texture_from_render: TransformGeneric<Float>, texture_from_render: Transform,
} }
impl CylindricalMapping { impl CylindricalMapping {
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self { pub fn new(texture_from_render: &Transform) -> Self {
Self { Self {
texture_from_render: *texture_from_render, texture_from_render: *texture_from_render,
} }
} }
}
impl TextureMapping2DTrait for CylindricalMapping { pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let pt = self.texture_from_render.apply_to_point(ctx.p); let pt = self.texture_from_render.apply_to_point(ctx.p);
let x2y2 = square(pt.x()) + square(pt.y()); let x2y2 = square(pt.x()) + square(pt.y());
let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2); let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2);
@ -162,7 +160,7 @@ impl TextureMapping2DTrait for CylindricalMapping {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PlanarMapping { pub struct PlanarMapping {
texture_from_render: TransformGeneric<Float>, texture_from_render: Transform,
vs: Vector3f, vs: Vector3f,
vt: Vector3f, vt: Vector3f,
ds: Float, ds: Float,
@ -171,7 +169,7 @@ pub struct PlanarMapping {
impl PlanarMapping { impl PlanarMapping {
pub fn new( pub fn new(
texture_from_render: &TransformGeneric<Float>, texture_from_render: &Transform,
vs: Vector3f, vs: Vector3f,
vt: Vector3f, vt: Vector3f,
ds: Float, ds: Float,
@ -185,10 +183,8 @@ impl PlanarMapping {
dt, dt,
} }
} }
}
impl TextureMapping2DTrait for PlanarMapping { pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
let vec: Vector3f = self.texture_from_render.apply_to_point(ctx.p).into(); let vec: Vector3f = self.texture_from_render.apply_to_point(ctx.p).into();
let dpdx = self.texture_from_render.apply_to_vector(ctx.dpdx); let dpdx = self.texture_from_render.apply_to_vector(ctx.dpdx);
let dpdy = self.texture_from_render.apply_to_vector(ctx.dpdy); let dpdy = self.texture_from_render.apply_to_vector(ctx.dpdy);
@ -208,9 +204,9 @@ impl TextureMapping2DTrait for PlanarMapping {
} }
pub struct TexCoord3D { pub struct TexCoord3D {
p: Point3f, pub p: Point3f,
dpdx: Vector3f, pub dpdx: Vector3f,
dpdy: Vector3f, pub dpdy: Vector3f,
} }
pub trait TextureMapping3DTrait { pub trait TextureMapping3DTrait {
@ -218,26 +214,31 @@ pub trait TextureMapping3DTrait {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[enum_dispatch(TextureMapping3DTrait)]
pub enum TextureMapping3D { pub enum TextureMapping3D {
PointTransform(PointTransformMapping), PointTransform(PointTransformMapping),
} }
#[derive(Clone, Debug)] impl TextureMapping3D {
pub struct PointTransformMapping { pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D {
texture_from_render: TransformGeneric<Float>, match self {
} TextureMapping3D::PointTransform(t) => t.map(ctx),
impl PointTransformMapping {
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
Self {
texture_from_render: *texture_from_render,
} }
} }
} }
impl TextureMapping3DTrait for PointTransformMapping { #[derive(Clone, Debug)]
fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D { pub struct PointTransformMapping {
texture_from_render: Transform,
}
impl PointTransformMapping {
pub fn new(texture_from_render: &Transform) -> Self {
Self {
texture_from_render: *texture_from_render,
}
}
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D {
TexCoord3D { TexCoord3D {
p: self.texture_from_render.apply_to_point(ctx.p), p: self.texture_from_render.apply_to_point(ctx.p),
dpdx: self.texture_from_render.apply_to_vector(ctx.dpdx), dpdx: self.texture_from_render.apply_to_vector(ctx.dpdx),
@ -323,34 +324,39 @@ impl From<&Interaction> for TextureEvalContext {
} }
} }
#[enum_dispatch]
pub trait FloatTextureTrait: Send + Sync + std::fmt::Debug {
fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
todo!()
}
}
#[enum_dispatch]
pub trait SpectrumTextureTrait: Send + Sync + std::fmt::Debug {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
}
#[repr(C)] #[repr(C)]
pub enum GPUFloatTexture { pub enum GPUFloatTexture {
Constant(Float), Constant(FloatConstantTexture),
DirectionMix(FloatDirectionMixTexture),
Scaled(FloatScaledTexture),
Bilerp(FloatBilerpTexture),
Checkerboard(FloatCheckerboardTexture),
Dots(FloatDotsTexture),
FBm(FBmTexture),
Windy(WindyTexture),
Wrinkled(WrinkledTexture),
Ptex(GPUFloatPtexTexture), Ptex(GPUFloatPtexTexture),
Image(GPUFloatImageTexture), Image(GPUFloatImageTexture),
Mix(GPUFloatMixTexture), Mix(GPUFloatMixTexture),
} }
#[derive(Clone, Debug)] impl GPUFloatTexture {
#[enum_dispatch(SpectrumTextureTrait)] pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
pub enum SpectrumTexture { match self {
Image(GPUSpectrumImageTexture), GPUFloatTexture::Constant(t) => t.evaluate(ctx),
Ptex(GPUSpectrumPtexTexture), GPUFloatTexture::DirectionMix(t) => t.evaluate(ctx),
Mix(GPUSpectrumMixTexture), GPUFloatTexture::Scaled(t) => t.evaluate(ctx),
GPUFloatTexture::Bilerp(t) => t.evaluate(ctx),
GPUFloatTexture::Checkerboard(t) => t.evaluate(ctx),
GPUFloatTexture::Dots(t) => t.evaluate(ctx),
GPUFloatTexture::FBm(t) => t.evaluate(ctx),
GPUFloatTexture::Windy(t) => t.evaluate(ctx),
GPUFloatTexture::Wrinkle(t) => t.evaluate(ctx),
GPUFloatTexture::Ptex(t) => t.evaluate(ctx),
GPUFloatTexture::Image(t) => t.evaluate(ctx),
GPUFloatTexture::Mix(t) => t.evaluate(ctx),
}
}
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -359,40 +365,34 @@ pub enum SpectrumType {
Albedo, Albedo,
Unbounded, Unbounded,
} }
pub trait TextureEvaluator: Send + Sync {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float;
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum;
fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool; #[repr(C)]
pub enum GPUSpectrumTexture {
Constant(SpectrumConstantTexture),
Bilerp(SpectrumBilerpTexture),
Checkerboard(SpectrumCheckerboardTexture),
Marble(MarbleTexture),
DirectionMix(SpectrumDirectionMixTexture),
Dots(SpectrumDotsTexture),
Scaled(SpectrumScaledTexture),
Image(GPUSpectrumImageTexture),
Ptex(GPUSpectrumPtexTexture),
Mix(GPUSpectrumMixTexture),
} }
#[derive(Copy, Clone, Default)] impl GPUSpectrumTexture {
pub struct UniversalTextureEvaluator; pub fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> Float {
match self {
impl TextureEvaluator for UniversalTextureEvaluator { GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float { GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda),
tex.evaluate(ctx) GPUSpectrumTexture::Checkerboard(t) => t.evaluate(ctx, lambda),
} GPUSpectrumTexture::Marble(t) => t.evaluate(ctx, lambda),
GPUSpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda),
fn evaluate_spectrum( GPUSpectrumTexture::Dots(t) => t.evaluate(ctx, lambda),
&self, GPUSpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda),
tex: &SpectrumTexture, GPUSpectrumTexture::Ptex(t) => t.evaluate(ctx, lambda),
ctx: &TextureEvalContext, GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda),
lambda: &SampledWavelengths, GPUSpectrumTexture::Mix(t) => t.evaluate(ctx, lambda),
) -> SampledSpectrum { }
tex.evaluate(ctx, lambda)
}
fn can_evaluate(
&self,
_float_textures: &[&FloatTexture],
_spectrum_textures: &[&SpectrumTexture],
) -> bool {
true
} }
} }

View file

@ -0,0 +1,38 @@
use crate::Float;
use crate::core::filter::FilterSample;
use crate::core::geometry::{Point2f, Vector2f};
#[derive(Clone, Debug)]
pub struct BoxFilter {
pub radius: Vector2f,
}
impl BoxFilter {
pub fn new(radius: Vector2f) -> Self {
Self { radius }
}
pub fn radius(&self) -> Vector2f {
self.radius
}
pub fn evaluate(&self, p: Point2f) -> Float {
if p.x().abs() <= self.radius.x() && p.y().abs() <= self.radius.y() {
1.
} else {
0.
}
}
pub fn integral(&self) -> Float {
(2.0 * self.radius.x()) * (2.0 * self.radius.y())
}
pub fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new(
lerp(u[0], -self.radius.x(), self.radius.x()),
lerp(u[1], -self.radius.y(), self.radius.y()),
);
FilterSample { p, weight: 1.0 }
}
}

View file

@ -0,0 +1,49 @@
#[derive(Clone, Debug)]
pub struct GaussianFilter {
pub radius: Vector2f,
pub sigma: Float,
pub exp_x: Float,
pub exp_y: Float,
pub sampler: FilterSampler,
}
impl GaussianFilter {
pub fn new(radius: Vector2f, sigma: Float) -> Self {
let exp_x = gaussian(radius.x(), 0., sigma);
let exp_y = gaussian(radius.y(), 0., sigma);
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let gx = (gaussian(p.x(), 0., sigma) - exp_x).max(0.0);
let gy = (gaussian(p.y(), 0., sigma) - exp_y).max(0.0);
gx * gy
});
Self {
radius,
sigma,
exp_x: gaussian(radius.x(), 0., sigma),
exp_y: gaussian(radius.y(), 0., sigma),
sampler,
}
}
pub fn radius(&self) -> Vector2f {
self.radius
}
pub fn evaluate(&self, p: Point2f) -> Float {
(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)
}
pub fn integral(&self) -> Float {
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
- 2.0 * self.radius.x() * self.exp_x)
* (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma)
- 2.0 * self.radius.y() * self.exp_y)
}
pub fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u)
}
}

View file

@ -0,0 +1,56 @@
#[derive(Clone, Debug)]
pub struct LanczosSincFilter {
pub radius: Vector2f,
pub tau: Float,
pub sampler: FilterSampler,
}
impl LanczosSincFilter {
pub fn new(radius: Vector2f, tau: Float) -> Self {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
});
Self {
radius,
tau,
sampler,
}
}
pub fn radius(&self) -> Vector2f {
self.radius
}
pub fn evaluate(&self, p: Point2f) -> Float {
windowed_sinc(p.x(), self.radius.x(), self.tau)
* windowed_sinc(p.y(), self.radius.y(), self.tau)
}
pub fn integral(&self) -> Float {
let sqrt_samples = 64;
let n_samples = sqrt_samples * sqrt_samples;
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
let mut sum = 0.0;
let mut rng = rand::rng();
for y in 0..sqrt_samples {
for x in 0..sqrt_samples {
let u = Point2f::new(
(x as Float + rng.random::<Float>()) / sqrt_samples as Float,
(y as Float + rng.random::<Float>()) / sqrt_samples as Float,
);
let p = Point2f::new(
lerp(u.x(), -self.radius.x(), self.radius.x()),
lerp(u.y(), -self.radius.y(), self.radius.y()),
);
sum += self.evaluate(p);
}
}
sum / n_samples as Float * area
}
pub fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u)
}
}

View file

@ -0,0 +1,67 @@
use crate::Float;
use crate::core::filter::FilterSampler;
use crate::core::geometry::{Point2f, Vector2f};
#[derive(Clone, Debug)]
pub struct MitchellFilter {
pub radius: Vector2f,
pub b: Float,
pub c: Float,
pub sampler: FilterSampler,
}
impl MitchellFilter {
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let nx = 2.0 * p.x() / radius.x();
let ny = 2.0 * p.y() / radius.y();
Self::mitchell_1d_eval(b, c, nx) * Self::mitchell_1d_eval(b, c, ny)
});
Self {
radius,
b,
c,
sampler,
}
}
fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
let x = x.abs();
if x <= 1.0 {
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
+ (6.0 - 2.0 * b))
* (1.0 / 6.0)
} else if x <= 2.0 {
((-b - 6.0 * c) * x.powi(3)
+ (6.0 * b + 30.0 * c) * x.powi(2)
+ (-12.0 * b - 48.0 * c) * x
+ (8.0 * b + 24.0 * c))
* (1.0 / 6.0)
} else {
0.0
}
}
fn mitchell_1d(&self, x: Float) -> Float {
Self::mitchell_1d_eval(self.b, self.c, x)
}
pub fn radius(&self) -> Vector2f {
self.radius
}
pub fn evaluate(&self, p: Point2f) -> Float {
self.mitchell_1d(2.0 * p.x() / self.radius.x())
* self.mitchell_1d(2.0 * p.y() / self.radius.y())
}
pub fn integral(&self) -> Float {
self.radius.x() * self.radius.y() / 4.0
}
pub fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u)
}
}

View file

@ -0,0 +1,5 @@
pub mod boxf;
pub mod gaussian;
pub mod lanczos;
pub mod mitchell;
pub mod triangle;

View file

@ -0,0 +1,30 @@
#[derive(Clone, Debug)]
pub struct TriangleFilter {
pub radius: Vector2f,
}
impl TriangleFilter {
pub fn new(radius: Vector2f) -> Self {
Self { radius }
}
pub fn radius(&self) -> Vector2f {
self.radius
}
pub fn evaluate(&self, p: Point2f) -> Float {
(self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0)
}
pub fn integral(&self) -> Float {
self.radius.x().powi(2) * self.radius.y().powi(2)
}
pub fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new(
sample_tent(u[0], self.radius.x()),
sample_tent(u[1], self.radius.y()),
);
FilterSample { p, weight: 1.0 }
}
}

View file

@ -5,6 +5,7 @@
mod cameras; mod cameras;
mod core; mod core;
mod data; mod data;
mod filters;
mod images; mod images;
mod integrators; mod integrators;
mod lights; mod lights;

View file

@ -3,7 +3,7 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum}; use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
use crate::core::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, CIE_X, CIE_Y, CIE_Z, N_CIES}; use crate::core::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, CIE_X, CIE_Y, CIE_Z, N_CIES};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::spectra::{BlackbodySpectrum, SpectrumProvider}; use crate::spectra::{BlackbodySpectrum, SpectrumTrait};
use crate::utils::math::square; use crate::utils::math::square;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;

View file

@ -7,10 +7,9 @@ pub mod sampled;
pub mod simple; pub mod simple;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::file::read_float_file;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub use color::{RGB, XYZ}; pub use color::{ColorEncoding, RGB, RGBSigmoidPolynomial, XYZ};
pub use colorspace::RGBColorSpace; pub use colorspace::RGBColorSpace;
pub use data::*; pub use data::*;
pub use rgb::*; pub use rgb::*;
@ -19,32 +18,33 @@ pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
pub use simple::*; pub use simple::*;
// //
#[enum_dispatch] #[enum_dispatch]
pub trait SpectrumProvider: Copy { pub trait SpectrumTrait: Copy {
fn evaluate(&self, lambda: Float) -> Float; fn evaluate(&self, lambda: Float) -> Float;
fn max_value(&self) -> Float; fn max_value(&self) -> Float;
} }
#[cfg(not(target_arch = "spirv"))] // #[cfg(not(target_arch = "spirv"))]
impl SpectrumProvider for std::sync::Arc<DenselySampledSpectrum> { // impl SpectrumTrait for std::sync::Arc<DenselySampledSpectrum> {
fn evaluate(&self, lambda: Float) -> Float { // fn evaluate(&self, lambda: Float) -> Float {
(**self).evaluate(lambda) // (**self).evaluate(lambda)
} // }
fn max_value(&self) -> Float { // fn max_value(&self) -> Float {
(**self).max_value() // (**self).max_value()
} // }
} // }
//
#[cfg(target_arch = "spirv")] // or target_os = "cuda" // #[cfg(target_arch = "spirv")] // or target_os = "cuda"
impl SpectrumProvider for u32 { // impl SpectrumTrait for u32 {
fn evaluate(&self, lambda: Float) -> Float { // fn evaluate(&self, lambda: Float) -> Float {
// Here you would call a global function that accesses // // Here you would call a global function that accesses
// a static buffer of spectra data // // a static buffer of spectra data
crate::gpu::lookup_global_spectrum(*self, lambda) // crate::gpu::lookup_global_spectrum(*self, lambda)
} // }
fn max_value(&self) -> Float { // fn max_value(&self) -> Float {
crate::gpu::lookup_global_spectrum_max(*self) // crate::gpu::lookup_global_spectrum_max(*self)
} // }
} // }
//
#[enum_dispatch(SpectrumTrait)] #[enum_dispatch(SpectrumTrait)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -1,6 +1,6 @@
use super::{ use super::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace,
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumProvider, XYZ, RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumTrait, XYZ,
}; };
use crate::Float; use crate::Float;
@ -26,7 +26,7 @@ impl RGBAlbedoSpectrum {
} }
} }
impl SpectrumProvider for RGBAlbedoSpectrum { impl SpectrumTrait for RGBAlbedoSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
self.rsp.evaluate(lambda) self.rsp.evaluate(lambda)
} }
@ -58,7 +58,7 @@ impl UnboundedRGBSpectrum {
} }
} }
impl SpectrumProvider for UnboundedRGBSpectrum { impl SpectrumTrait for UnboundedRGBSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
self.scale * self.rsp.evaluate(lambda) self.scale * self.rsp.evaluate(lambda)
} }
@ -69,10 +69,10 @@ impl SpectrumProvider for UnboundedRGBSpectrum {
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct RGBIlluminantSpectrum<P: SpectrumProvider> { pub struct RGBIlluminantSpectrum {
scale: Float, scale: Float,
rsp: RGBSigmoidPolynomial, rsp: RGBSigmoidPolynomial,
illuminant: P, illuminant: DenselySampledSpectrum,
} }
// impl RGBIlluminantSpectrum { // impl RGBIlluminantSpectrum {
@ -101,7 +101,7 @@ pub struct RGBIlluminantSpectrum<P: SpectrumProvider> {
// } // }
// } // }
impl SpectrumProvider for RGBIlluminantSpectrum { impl SpectrumTrait for RGBIlluminantSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
match &self.illuminant { match &self.illuminant {
Some(illuminant) => { Some(illuminant) => {
@ -161,7 +161,7 @@ impl RGBUnboundedSpectrum {
} }
} }
impl SpectrumProvider for RGBUnboundedSpectrum { impl SpectrumTrait for RGBUnboundedSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
self.scale * self.rsp.evaluate(lambda) self.scale * self.rsp.evaluate(lambda)
} }

View file

@ -2,7 +2,7 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::core::cie::*; use crate::core::cie::*;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::spectra::{ use crate::spectra::{
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider, N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
}; };
use crate::utils::file::read_float_file; use crate::utils::file::read_float_file;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -21,7 +21,7 @@ impl ConstantSpectrum {
} }
} }
impl SpectrumProvider for ConstantSpectrum { impl SpectrumTrait for ConstantSpectrum {
fn evaluate(&self, _lambda: Float) -> Float { fn evaluate(&self, _lambda: Float) -> Float {
self.c self.c
} }
@ -165,7 +165,7 @@ impl Hash for DenselySampledSpectrum {
} }
} }
impl SpectrumProvider for DenselySampledSpectrum { impl SpectrumTrait for DenselySampledSpectrum {
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() { if offset < 0 || offset as usize >= self.values.len() {
@ -241,7 +241,7 @@ impl PiecewiseLinearSpectrum {
} }
} }
impl SpectrumProvider 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_empty() {
return 0.0; return 0.0;
@ -321,7 +321,7 @@ impl BlackbodySpectrum {
} }
} }
impl SpectrumProvider for BlackbodySpectrum { impl SpectrumTrait for BlackbodySpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
Self::planck_law(lambda, self.temperature) * self.normalization_factor Self::planck_law(lambda, self.temperature) * self.normalization_factor
} }

View file

@ -0,0 +1,85 @@
use crate::core::texture::{TextureEvalContext, TextureMapping2D};
use crate::spectra::{SampledSpectrum, SampledWavelengths, SpectrumTrait};
use crate::utils::Transform;
#[derive(Debug, Clone)]
pub struct FloatBilerpTexture {
mapping: TextureMapping2D,
v00: Float,
v01: Float,
v10: Float,
v11: Float,
}
#[inline(always)]
pub fn bilerp(st_frac: [Float; 2], v: [SampledSpectrum; 4]) -> SampledSpectrum {
let sx = st_frac[0];
let sy = st_frac[1];
let one_minus_sx = 1.0 - sx;
let one_minus_sy = 1.0 - sy;
one_minus_sx * one_minus_sy * v[0]
+ sx * one_minus_sy * v[1]
+ one_minus_sx * sy * v[2]
+ sx * sy * v[3]
}
impl FloatBilerpTexture {
pub fn new(mapping: TextureMapping2D, v00: Float, v01: Float, v10: Float, v11: Float) -> Self {
Self {
mapping,
v00,
v01,
v10,
v11,
}
}
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct SpectrumBilerpTexture {
pub mapping: TextureMapping2D,
pub v00: Spectrum,
pub v01: Spectrum,
pub v10: Spectrum,
pub v11: Spectrum,
}
impl SpectrumBilerpTexture {
pub fn new(
mapping: TextureMapping2D,
v00: Spectrum,
v01: Spectrum,
v10: Spectrum,
v11: Spectrum,
) -> Self {
Self {
mapping,
v00,
v01,
v10,
v11,
}
}
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
let c = self.mapping.map(ctx);
bilerp(
[c.st[0], c.st[1], c.st[2]],
[
v00.sample(lambda),
v01.sample(lambda),
v10.sample(lambda),
v11.sample(lambda),
],
)
}
}

View file

@ -0,0 +1,29 @@
use crate::core::texture::TextureEvalContext;
use crate::spectra::{SampledSpectrum, SampledWavelengths};
// TODO: I have to implement somethign like a TaggedPointer, and change the whole codebase.
// Fantastic
#[derive(Debug, Clone)]
pub struct FloatCheckerboardTexture {
pub map_2d: TextureMapping2D,
pub map_3d: TextureMapping3D,
pub tex: [FloatTexture; 2],
}
impl FloatCheckerboardTexture {
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct SpectrumCheckerboardTexture;
impl SpectrumCheckerboardTexture {
pub fn evaluate(
&self,
_ctx: &TextureEvalContext,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
}

View file

@ -0,0 +1,37 @@
use crate::Float;
use crate::core::texture::TextureEvalContext;
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum};
#[derive(Debug, Clone)]
pub struct FloatConstantTexture {
pub value: Float,
}
impl FloatConstantTexture {
pub fn new(value: Float) -> Self {
Self { value }
}
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
self.value
}
}
#[derive(Clone, Debug)]
pub struct SpectrumConstantTexture {
pub value: Spectrum,
}
impl SpectrumConstantTexture {
pub fn new(value: Spectrum) -> Self {
Self { value }
}
pub fn evaluate(
&self,
_ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
self.value.sample(lambda)
}
}

View file

@ -0,0 +1,19 @@
#[derive(Debug, Clone)]
pub struct FloatDotsTexture;
impl FloatDotsTexture {
pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct SpectrumDotsTexture;
impl SpectrumDotsTexture {
pub fn evaluate(
&self,
_ctx: &TextureEvalContext,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
}

View file

@ -0,0 +1,15 @@
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
#[derive(Debug, Clone)]
pub struct FBmTexture {
pub mapping: TextureMapping3D,
pub omega: Float,
pub octaves: usize,
}
impl FBmTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let c = self.mapping.map(ctx);
fbm(c.p, c.dpdx, c.dpdy, self.omega, self.octaves)
}
}

View file

@ -1,9 +1,6 @@
use crate::{ use crate::Float;
core::texture::{ use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
FloatTextureTrait, ImageTextureBase, SpectrumTextureTrait, SpectrumType, TextureMapping2D, use crate::spectra::RGBColorSpace;
},
spectra::RGBColorSpace,
};
/* 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 */
@ -15,13 +12,59 @@ pub struct GPUSpectrumImageTexture {
pub tex_obj: u64, pub tex_obj: u64,
pub scale: Float, pub scale: Float,
pub invert: bool, pub invert: bool,
pub is_single_channel: bool,
pub color_space: RGBColorSpace, pub color_space: RGBColorSpace,
pub spectrum_type: SpectrumType, pub spectrum_type: SpectrumType,
} }
impl SpectrumTextureTrait for GPUSpectrumImageTexture { impl GPUSpectrumImageTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { pub fn evaluate(
todo!() &self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
#[cfg(not(feature = "cuda"))]
{
return SampledSpectrum::zero();
}
#[cfg(feature = "cuda")]
{
use cuda_std::intrinsics;
let c = self.mapping.map(ctx);
let u = c.st.x;
let v = 1.0 - c.st.y;
let d_p_dx = [c.dsdx, c.dtdx];
let d_p_dy = [c.dsdy, c.dtdy];
let tex_color = if self.is_single_channel {
let val: Float =
unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
RGB::new(val, val, val)
} else {
let val: [Float; 4] =
unsafe { intrinsics::tex2d_grad(self.tex_obj, u, v, d_p_dx, d_p_dy) };
RGB::new(val[0], val[1], val[2])
};
let mut rgb = tex_color * self.scale;
if self.invert {
rgb = (RGB::new(1.0, 1.0, 1.0) - rgb).clamp_zero();
}
let color_space = unsafe { &*self.color_space };
match self.spectrum_type {
SpectrumType::Unbounded => {
RGBUnboundedSpectrum::new(color_space, rgb).sample(lambda)
}
SpectrumType::Albedo => {
RGBAlbedoSpectrum::new(color_space, rgb.clamp(0.0, 1.0)).sample(lambda)
}
_ => RGBIlluminantSpectrum::new(color_space, rgb).sample(lambda),
}
}
} }
} }
@ -33,4 +76,27 @@ pub struct GPUFloatImageTexture {
pub invert: bool, pub invert: bool,
} }
impl FloatTextureTrait for GPUFloatImageTexture {} impl GPUFloatImageTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
#[cfg(not(feature = "cuda"))]
{
return 0.;
}
#[cfg(feature = "cuda")]
{
use cuda_std::intrinsics;
let c = self.mapping.map(ctx);
let u = c.st.x;
let v = 1.0 - c.st.y;
let d_p_dx = [c.dsdx, c.dtdx];
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) };
if invert {
return (1. - v).max(0.);
} else {
return v;
}
}
}
}

View file

@ -0,0 +1,56 @@
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;
#[derive(Clone, Debug)]
pub struct MarbleTexture {
pub mapping: TextureMapping3D,
pub octaves: usize,
pub omega: Float,
pub scale: Float,
pub variation: Float,
}
impl MarbleTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
let mut c = self.mapping.map(ctx);
c.p *= self.scale;
let marble = c.p.y() + self.variation * fbm(c.p, self.scale, c.dpdy, omega, self.octaves);
const COLORS: [RGB; 9] = [
RGB::new(0.58, 0.58, 0.6),
RGB::new(0.58, 0.58, 0.6),
RGB::new(0.58, 0.58, 0.6),
RGB::new(0.5, 0.5, 0.5),
RGB::new(0.6, 0.59, 0.58),
RGB::new(0.58, 0.58, 0.6),
RGB::new(0.58, 0.58, 0.6),
RGB::new(0.2, 0.2, 0.33),
RGB::new(0.58, 0.58, 0.6),
];
const N_SEG: i32 = 6; // (9 - 3)
let t_clamped = t.clamp(0.0, 1.0);
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 first_idx = first as usize;
let rgb = evaluate_cubic_bezier(&COLORS[first_idx..first_idx + 4], t_segment) * 1.5;
let color_space = {
#[cfg(target_os = "cuda")]
{
unsafe { &*RGBColorSpace_sRGB }
}
#[cfg(not(target_os = "cuda"))]
{
RGBColorSpace::srgb()
}
};
RGBAlbedoSpectrum::new(color_space, rgb).sample(lambda)
}
}

View file

@ -1,3 +1,23 @@
pub mod bilerp;
pub mod checkerboard;
pub mod constant;
pub mod dots;
pub mod fbm;
pub mod image; pub mod image;
pub mod marble;
pub mod mix; pub mod mix;
pub mod ptex; pub mod ptex;
pub mod windy;
pub mod wrinkled;
pub use bilerp::*;
pub use checkerboard::*;
pub use constant::*;
pub use dots::*;
pub use fbm::*;
pub use image::*;
pub use marble::*;
pub use mix::*;
pub use ptex::*;
pub use windy::*;
pub use wrinkled::*;

View file

@ -1,16 +1,55 @@
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext}; use crate::Float;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::core::texture::{SpectrumType, TextureEvalContext};
use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths,
};
/* GPU heavy code, have to see how to best approach this /* GPU heavy code, have to see how to best approach this
*/ */
#[derive(Clone, Debug)]
pub struct GPUSpectrumPtexTexture; #[derive(Debug, Clone)]
impl SpectrumTextureTrait for GPUSpectrumPtexTexture { pub struct GPUFloatPtexTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { pub face_values: Vec<Float>,
todo!() }
impl GPUFloatPtexTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
self.face_values[ctx.face_index]
} }
} }
#[derive(Debug, Clone)] #[derive(Clone, Debug)]
pub struct GPUFloatPtexTexture; pub struct GPUSpectrumPtexTexture {
impl FloatTextureTrait for GPUFloatPtexTexture {} pub face_values: *const RGB,
pub n_faces: usize,
pub spectrum_type: SpectrumType,
}
impl GPUSpectrumPtexTexture {
pub fn evaluate(
&self,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
let index = ctx.face_index.clamp(0, self.n_faces.saturating_sub(1));
let rgb = unsafe { *self.face_values.add(index) };
let s_rgb = {
#[cfg(feature = "cuda")]
unsafe {
&*RGBColorSpace_sRGB
}
#[cfg(not(feature = "cuda"))]
RGBColorSpace::srgb()
};
match self.spectrum_type {
SpectrumType::Unbounded => RGBUnboundedSpectrum::new(s_rgb, rgb).sample(lambda),
SpectrumType::Albedo => {
let clamped_rgb = rgb.clamp(0.0, 1.0);
RGBAlbedoSpectrum::new(s_rgb, clamped_rgb).sample(lambda)
}
SpectrumType::Illuminant => RGBIlluminantSpectrum::new(s_rgb, rgb).sample(lambda),
}
}
}

View file

@ -0,0 +1,16 @@
use crate::core::texture::{TextureEvalContext, TextureMapping3D};
use crate::utils::noise::fbm;
#[derive(Debug, Clone)]
pub struct WindyTexture {
pub mapping: TextureMapping3D,
}
impl WindyTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
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 wave_height = fbm(c.p, c.dpdx, c.dpdy, 0.5, 6);
wind_strength.abs() * wave_height
}
}

View file

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

View file

@ -20,134 +20,161 @@ use crate::core::geometry::{
// { // {
// } // }
#[derive(Debug, Clone)] #[repr(C)]
pub struct Array2D<T> { pub struct Array2D<T> {
pub values: Vec<T>, pub values: *mut T,
pub extent: Bounds2i, pub extent: Bounds2i,
} }
impl<T> Array2D<T> { impl<T> Array2D<T> {
pub fn new(extent: Bounds2i) -> Self #[inline]
where pub fn x_size(&self) -> usize {
T: Default, (self.extent.p_max.x() - self.extent.p_min.x()) as usize
{
let size = extent.area() as usize;
let mut values = Vec::with_capacity(size);
values.resize_with(size, T::default);
Self { values, extent }
} }
pub fn new_with_dims(nx: usize, ny: usize) -> Self #[inline]
where pub fn y_size(&self) -> usize {
T: Default, (self.extent.p_max.y() - self.extent.p_min.y()) as usize
{
Self::new(Bounds2i::from_points(
Point2i::new(0, 0),
Point2i::new(nx as i32, ny as i32),
))
} }
pub fn new_filled(width: usize, height: usize, value: T) -> Self #[inline]
where pub fn size(&self) -> usize {
T: Clone, self.extent.area() as usize
{ }
#[inline]
pub fn index(&self, x: i32, y: i32) -> usize {
let nx = x - self.extent.p_min.x;
let ny = y - self.extent.p_min.y;
(nx + self.x_size() * ny) as usize
}
#[inline]
pub unsafe fn get(&self, x: i32, y: i32) -> &T {
unsafe { &*self.values.add(self.index(x, y)) }
}
#[inline]
pub unsafe fn get_mut(&mut self, x: i32, y: i32) -> &mut T {
unsafe { &mut *self.values.add(self.index(x, y)) }
}
#[inline]
pub fn get_linear(&self, index: usize) -> &T {
unsafe { &*self.values.add(index) }
}
#[inline]
pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
// SAFETY: Caller must ensure index < size()
unsafe { &mut *self.values.add(index) }
}
pub fn as_slice(&self) -> &[T] {
unsafe { core::slice::from_raw_parts(self.values, self.size()) }
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.values, self.size()) }
}
}
#[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( let extent = Bounds2i::from_points(
Point2i::new(0, 0), Point2i::new(0, 0),
Point2i::new(width as i32, height as i32), Point2i::new(width as i32, height as i32),
); );
Self::new_from_bounds(extent, value) Self::new_from_bounds(extent, value)
} }
pub fn new_from_bounds(extent: Bounds2i, default_val: T) -> Self
where
T: Clone,
{
let size = extent.area() as usize;
let values = vec![default_val; size];
Self { values, extent }
}
pub fn x_size(&self) -> usize {
(self.extent.p_max.x() - self.extent.p_min.x()) as usize
}
pub fn y_size(&self) -> usize {
(self.extent.p_max.y() - self.extent.p_min.y()) as usize
}
pub fn size(&self) -> usize {
self.values.len()
}
pub fn as_slice(&self) -> &[T] {
&self.values
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.values
}
fn get_index(&self, p: Point2i) -> usize {
debug_assert!(p.x() >= self.extent.p_min.x() && p.x() < self.extent.p_max.x());
debug_assert!(p.y() >= self.extent.p_min.y() && p.y() < self.extent.p_max.y());
let width = self.x_size();
let pp = Point2i::new(p.x() - self.extent.p_min.x(), p.y() - self.extent.p_min.y());
(pp.y() * width as i32 + pp.x()) as usize
}
pub fn get_linear(&self, index: usize) -> &T {
&self.values[index]
}
pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
&mut self.values[index]
}
} }
#[cfg(not(feature = "cuda"))]
impl<T> Index<Point2i> for Array2D<T> { impl<T> Index<Point2i> for Array2D<T> {
type Output = T; type Output = T;
fn index(&self, mut p: Point2i) -> &Self::Output { fn index(&self, mut p: Point2i) -> &Self::Output {
p -= Vector2i::from(self.extent.p_min); unsafe { self.get(pos.0, pos.1) }
let width = self.extent.p_max.x() - self.extent.p_min.x();
let idx = (p.x() + width * p.y()) as usize;
&self.values[idx]
} }
} }
#[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, mut p: Point2i) -> &mut Self::Output {
p -= Vector2i::from(self.extent.p_min); unsafe { self.get_mut(pos.0, pos.1) }
let width = self.extent.p_max.x() - self.extent.p_min.x();
let idx = (p.x() + width * p.y()) as usize;
&mut self.values[idx]
}
}
impl<T> Index<(usize, usize)> for Array2D<T> {
type Output = T;
fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
&self[(x as i32, y as i32)]
}
}
impl<T> IndexMut<(usize, usize)> for Array2D<T> {
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output {
&mut self[(x as i32, y as i32)]
} }
} }
#[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, index: (i32, i32)) -> &Self::Output { fn index(&self, pos: (i32, i32)) -> &Self::Output {
self.index(Point2i::new(index.0, index.1)) unsafe { self.get(pos.0, pos.1) }
} }
} }
#[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, index: (i32, i32)) -> &mut Self::Output { fn index_mut(&mut self, pos: (i32, i32)) -> &mut Self::Output {
self.index_mut(Point2i::new(index.0, index.1)) unsafe { self.get_mut(pos.0, pos.1) }
} }
} }

View file

@ -6,6 +6,7 @@ pub mod hash;
pub mod interval; pub mod interval;
pub mod math; pub mod math;
pub mod mesh; pub mod mesh;
pub mod noise;
pub mod quaternion; pub mod quaternion;
pub mod rng; pub mod rng;
pub mod sampling; pub mod sampling;
@ -30,48 +31,56 @@ where
i i
} }
#[derive(Debug)] #[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct AtomicFloat { pub struct AtomicFloat {
bits: AtomicU64, value: f64,
} }
impl AtomicFloat { impl AtomicFloat {
pub fn new(value: f64) -> Self { pub fn new(value: f64) -> Self {
Self { Self { value }
bits: AtomicU64::new(value.to_bits()),
}
} }
pub fn load(&self) -> f64 { pub fn load(&self) -> f64 {
f64::from_bits(self.bits.load(Ordering::Relaxed)) #[cfg(not(target_os = "cuda"))]
{
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")]
self.value
} }
pub fn store(&self, value: f64) { pub fn add(&self, v: f64) {
self.bits.store(value.to_bits(), Ordering::Relaxed); let ptr = &self.value as *const f64 as *mut f64;
}
pub fn add(&self, value: f64) { #[cfg(target_os = "cuda")]
let mut current_bits = self.bits.load(Ordering::Relaxed); unsafe {
loop { cuda_std::intrinsics::atomic_add(ptr, v);
let current_val = f64::from_bits(current_bits); }
let new_val = current_val + value;
let new_bits = new_val.to_bits();
match self.bits.compare_exchange_weak( #[cfg(not(target_os = "cuda"))]
current_bits, unsafe {
new_bits, use core::sync::atomic::{AtomicU64, Ordering};
Ordering::Relaxed, let atomic_ptr = ptr as *const AtomicU64;
Ordering::Relaxed, let atomic = &*atomic_ptr;
) { let mut current_bits = atomic.load(Ordering::Relaxed);
Ok(_) => break, loop {
Err(x) => current_bits = x, let current_val = f64::from_bits(current_bits);
let new_val = current_val + v;
match atomic.compare_exchange_weak(
current_bits,
new_val.to_bits(),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(x) => current_bits = x,
}
} }
} }
} }
} }
impl Default for AtomicFloat {
fn default() -> Self {
Self::new(0.0)
}
}

179
shared/src/utils/noise.rs Normal file
View file

@ -0,0 +1,179 @@
use crate::Float;
use crate::core::geometry::{Point3f, Vector3f, VectorLike};
use crate::utils::math::{clamp, lerp, smooth_step};
static NOISE_PERM_SIZE: usize = 256;
static NOISE_PERM: [i32; 2 * NOISE_PERM_SIZE] = [
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
142, // Remainder of the noise permutation table
8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203,
117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74,
165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220,
105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132,
187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3,
64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59,
227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70,
221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232,
178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162,
241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204,
176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141,
128, 195, 78, 66, 215, 61, 156, 180, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194,
233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234,
75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174,
20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83,
111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25,
63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188,
159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147,
118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170,
213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253,
19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,
238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31,
181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
];
#[inline(always)]
fn noise_weight(t: Float) -> Float {
let t2 = t * t;
let t3 = t2 * t;
let t4 = t3 * t;
let t5 = t4 * t;
6.0 * t5 - 15.0 * t4 + 10.0 * t3
}
#[inline(always)]
fn grad(x: i32, y: i32, z: i32, dx: Float, dy: Float, dz: Float) -> Float {
let hash =
NOISE_PERM[NOISE_PERM[NOISE_PERM[x as usize] as usize + y as usize] as usize + z as usize];
let h = h & 15;
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 mut result = 0.0;
if (h & 1) != 0 {
result -= u;
} else {
result += u;
}
if (h & 2) != 0 {
result -= v;
} else {
result += v;
}
result
}
#[inline(always)]
pub fn noise_from_point(mut p: Point3f) -> Float {
noise(p.x(), p.y(), p.z())
}
#[inline(always)]
pub fn noise(mut x: Float, mut y: Float, mut z: Float) -> Float {
let max_coord = (1i32 << 30) as Float;
x = x % max_coord;
y = y % max_coord;
z = z % max_coord;
if x < 0.0 {
x += max_coord;
}
if y < 0.0 {
y += max_coord;
}
if z < 0.0 {
z += max_coord;
}
let ix = x.floor() as i32;
let iy = y.floor() as i32;
let iz = z.floor() as i32;
let dx = x - ix as Float;
let dy = y - iy as Float;
let dz = z - iz as Float;
let ix = ix & (NOISE_PERM_SIZE as i32 - 1);
let iy = iy & (NOISE_PERM_SIZE as i32 - 1);
let iz = iz & (NOISE_PERM_SIZE as i32 - 1);
// Fetch gradients
let w000 = grad(ix, iy, iz, dx, 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 w110 = grad(ix + 1, iy + 1, iz, dx - 1, dy - 1, dz);
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 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 wx = noise_weight(dx);
let wy = noise_weight(dy);
let wz = noise_weight(dz);
let x00 = lerp(wx, w000, w100);
let x10 = lerp(wx, w010, w110);
let x01 = lerp(wx, w001, w101);
let x11 = lerp(wx, w011, w111);
let y0 = lerp(wy, x00, x10);
let y1 = lerp(wy, x01, x11);
lerp(wz, y0, y1)
}
pub fn fbm(p: Point3f, dpdx: Vector3f, dpdy: Vector3f, omega: Float, max_octaves: usize) -> Float {
// Compute number of octaves for antialiased FBm
let len2 = dpdx.norm_squared().max(dpdy.norm_squared());
let n = clamp(-1. - len.log2() / 2., 0., max_octaves);
let n_int = n.floor();
let mut sum = 0.;
let mut lambda = 1.;
let mut o = 1;
for i in 0..n_int {
sum += o * Noise(lambda * p);
lambda *= 1.99;
o *= omega;
}
let n_partial = n - n_int;
sum += o * smooth_step(n_partial, 0.3, 0.7) * noise_from_point(lambda * p);
return sum;
}
pub fn turbulence(
p: Point3f,
dpdx: Vector3f,
dpdy: Vector3f,
omega: Float,
max_octaves: usize,
) -> Float {
// Compute number of octaves for antialiased FBm
let len2 = dpdx.norm_squared().max(dpdy.norm_squared());
let n = clamp(-1. - len2.log2() / 2., 0, maxOctaves);
let n_int = n.floor();
// Compute sum of octaves of noise for turbulence
let mut sum = 0.;
let mut lambda = 1.;
let mut o = 1.;
for i in 0..n_int {
sum += o * noise_from_point(lambda * p).abs();
lambda *= 1.99;
o *= omega;
}
let n_partial = n - n_int;
sum += o * lerp(
smooth_step(n_partial, 0.3, 0.7),
0.2,
noise_from_point(lambda * p).abs(),
);
for i in n_int..max_octaves {
sum += o * 0.2;
o *= omega;
}
return sum;
}

View file

@ -700,64 +700,84 @@ impl VarianceEstimator {
} }
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone, Default)]
#[repr(C)]
pub struct PLSample { pub struct PLSample {
pub p: Point2f, pub p: Point2f,
pub pdf: Float, pub pdf: Float,
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct PiecewiseConstant1D { pub struct PiecewiseConstant1D {
pub func: Vec<Float>, pub func: *mut Float,
pub cdf: Vec<Float>, pub cdf: *mut Float,
pub min: Float, pub min: Float,
pub max: Float, pub max: Float,
pub n: usize,
pub func_integral: Float, pub func_integral: Float,
} }
#[cfg(not(target_os = "cuda"))]
impl PiecewiseConstant1D { impl PiecewiseConstant1D {
pub fn new(f: &[Float]) -> Self {
Self::new_with_bounds(f, 0., 1.)
}
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self { pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
assert!(max > min);
let n = f.len(); let n = f.len();
let mut func = Vec::with_capacity(n); let mut func_vec = f.to_vec();
for &val in f { let mut cdf_vec = vec![0.0; n + 1];
func.push(val.abs());
}
let mut cdf = vec![0.; n + 1]; cdf_vec[0] = 0.0;
for i in 1..=n { for i in 1..=n {
debug_assert!(func[i - 1] >= 0.); cdf_vec[i] = cdf_vec[i - 1] + func_vec[i - 1] / n as Float;
cdf[i] = cdf[i - 1] + func[i - 1] * (max - min) / n as Float; }
let func_int = cdf_vec[n];
if func_int > 0.0 {
for i in 1..=n {
cdf_vec[i] /= func_int;
}
} else {
for i in 1..=n {
cdf_vec[i] = i as Float / n as Float;
}
} }
let func_integral = cdf[n]; let func = func_vec.as_mut_ptr();
if func_integral == 0. { let cdf = cdf_vec.as_mut_ptr();
let n_float = n as Float; std::mem::forget(func_vec);
cdf.iter_mut() std::mem::forget(cdf_vec);
.enumerate()
.for_each(|(i, c)| *c = i as Float / n_float);
} else {
let inv_integral = 1.0 / func_integral;
cdf.iter_mut().for_each(|c| *c *= inv_integral);
}
Self { Self {
func, func,
cdf, cdf,
func_integral,
min, min,
max, max,
n,
func_integral,
} }
} }
pub fn new(f: &[Float]) -> Self {
Self::new_with_bounds(f, 0., 1.)
}
}
#[cfg(not(target_os = "cuda"))]
impl Drop for PiecewiseConstant1D {
fn drop(&mut self) {
if !self.func.is_null() {
unsafe {
let _ = Vec::from_raw_parts(self.func, self.n, self.n);
let _ = Vec::from_raw_parts(self.cdf, self.n + 1, self.n + 1);
}
}
}
}
impl PiecewiseConstant1D {
pub fn integral(&self) -> Float { pub fn integral(&self) -> Float {
self.func_integral self.func_integral
} }
pub fn size(&self) -> usize { pub fn size(&self) -> usize {
self.func.len() self.n
} }
pub fn sample(&self, u: Float) -> (Float, Float, usize) { pub fn sample(&self, u: Float) -> (Float, Float, usize) {
@ -776,37 +796,46 @@ impl PiecewiseConstant1D {
(value, pdf_val, o) (value, pdf_val, o)
} }
} }
#[derive(Debug, Clone)]
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct PiecewiseConstant2D { pub struct PiecewiseConstant2D {
pub p_conditional_v: Vec<PiecewiseConstant1D>,
pub p_marginal: PiecewiseConstant1D,
pub domain: Bounds2f, pub domain: Bounds2f,
pub p_conditional_v: *mut PiecewiseConstant1D,
pub p_marginal: PiecewiseConstant1D,
pub n_conditionals: usize,
} }
#[cfg(not(target_os = "cuda"))]
impl PiecewiseConstant2D { impl PiecewiseConstant2D {
pub fn new(data: &Array2D<Float>, nu: usize, nv: usize, domain: Bounds2f) -> Self { pub fn new(data: &Array2D<Float>, nu: usize, nv: usize, domain: Bounds2f) -> Self {
let mut p_conditional_v = Vec::with_capacity(nv); let nu = data.x_size() as usize;
let nv = data.y_size() as usize;
let mut conditionals = Vec::with_capacity(nv);
for v in 0..nv { for v in 0..nv {
let start = v * nu; let row = unsafe { core::slice::from_raw_parts(data.values.add(v * nu), nu) };
let end = start + nu; conditionals.push(PiecewiseConstant1D::new_with_bounds(
p_conditional_v.push(PiecewiseConstant1D::new_with_bounds( row,
&data.as_slice()[start..end],
domain.p_min.x(), domain.p_min.x(),
domain.p_max.x(), domain.p_max.x(),
)); ));
} }
let marginal_func: Vec<Float> = p_conditional_v.iter().map(|p| p.integral()).collect(); let marginal_funcs: Vec<Float> = conditionals.iter().map(|c| c.func_integral).collect();
let p_marginal = PiecewiseConstant1D::new_with_bounds( let p_marginal = PiecewiseConstant1D::new_with_bounds(
&marginal_func, &marginal_funcs,
domain.p_min.y(), domain.p_min.y(),
domain.p_max.y(), domain.p_max.y(),
); );
let p_conditional_v = conditionals.as_mut_ptr();
std::mem::forget(conditionals);
Self { Self {
p_conditional_v, p_conditional_v,
p_marginal, p_marginal,
domain, domain,
n_conditionals: nv,
} }
} }
@ -817,14 +846,28 @@ impl PiecewiseConstant2D {
pub fn new_with_data(data: &Array2D<Float>) -> Self { pub fn new_with_data(data: &Array2D<Float>) -> Self {
let nx = data.x_size(); let nx = data.x_size();
let ny = data.y_size(); let ny = data.y_size();
Self::new( let domain = Bounds2f::new(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
data,
nx,
ny,
Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)),
)
}
Self::new(data, nx, ny, domain)
}
}
#[cfg(not(target_os = "cuda"))]
impl Drop for PiecewiseConstant2D {
fn drop(&mut self) {
if !self.p_conditional_v.is_null() {
unsafe {
let _ = Vec::from_raw_parts(
self.p_conditional_v,
self.n_conditionals,
self.n_conditionals,
);
}
}
}
}
impl PiecewiseConstant2D {
pub fn resolution(&self) -> Point2i { pub fn resolution(&self) -> Point2i {
Point2i::new( Point2i::new(
self.p_conditional_v[0].size() as i32, self.p_conditional_v[0].size() as i32,

349
src/core/film.rs Normal file
View file

@ -0,0 +1,349 @@
use shared::film::{Film, FilmBase, GBufferFilm, PixelSensor, RGBFilm, SpectralFilm};
const N_SWATCH_REFLECTANCES: usize = 24;
const SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> = Lazy::new(|| {
std::array::from_fn(|i| {
let raw_data = crate::core::cie::SWATCHES_RAW[i];
let pls = PiecewiseLinearSpectrum::from_interleaved(raw_data, false);
Spectrum::PiecewiseLinear(pls)
})
});
pub trait PixelSensorHost {
pub fn get_swatches() -> &[Spectrum; N_SWATCH_REFLECTANCES] {
&*SWATCH_REFLECTANCES
}
pub fn create(
params: &ParameterDictionary,
output_colorspace: Arc<RGBColorSpace>,
exposure_time: Float,
loc: &FileLoc,
) -> Result<Self, String> {
let iso = params.get_one_float("iso", 100.);
let mut white_balance_temp = params.get_one_float("whitebalance", 0.);
let sensor_name = params.get_one_string("sensor", "cie1931");
if sensor_name != "cie1931" && white_balance_temp == 0. {
white_balance_temp = 6500.;
}
let imaging_ratio = exposure_time * iso / 100.;
let d_illum = if white_balance_temp == 0. {
generate_cie_d(6500.)
} else {
generate_cie_d(white_balance_temp)
};
let sensor_illum: Option<Arc<Spectrum>> = if white_balance_temp != 0. {
Some(Arc::new(Spectrum::DenselySampled(d_illum)))
} else {
None
};
if sensor_name == "cie1931" {
return Ok(PixelSensor::new_with_white_balance(
output_colorspace,
sensor_illum,
imaging_ratio,
));
} else {
let r_opt = get_named_spectrum(&format!("{}_r", sensor_name));
let g_opt = get_named_spectrum(&format!("{}_g", sensor_name));
let b_opt = get_named_spectrum(&format!("{}_b", sensor_name));
if r_opt.is_none() || g_opt.is_none() || b_opt.is_none() {
return Err(format!(
"{}: unknown sensor type '{}' (missing RGB spectral data)",
loc, sensor_name
)
.into());
}
let r = Arc::new(r_opt.unwrap());
let g = Arc::new(g_opt.unwrap());
let b = Arc::new(b_opt.unwrap());
return PixelSensor::new(
r,
g,
b,
output_colorspace.clone(),
sensor_illum,
imaging_ratio,
)
.map_err(|e| e.to_string());
}
}
}
pub trait FilmBaseHost {
fn create(
params: &ParameterDictionary,
filter: Filter,
sensor: Option<&PixelSensor>,
loc: &FileLoc,
) -> Self;
}
impl FilmBaseHost for FilmBase {
fn create(
params: &ParameterDictionary,
filter: Filter,
sensor: Option<PixelSensor>,
loc: &FileLoc,
) -> Self {
let x_res = params.get_one_int("xresolution", 1280);
let y_res = params.get_one_int("yresolution", 720);
if x_res <= 0 || y_res <= 0 {
eprintln!(
"{}: Film resolution must be > 0. Defaulting to 1280x720.",
loc
);
}
let full_resolution = Point2i::new(x_res.max(1), y_res.max(1));
let crop_data = params.get_float_array("cropwindow");
let crop = if crop_data.len() == 4 {
Bounds2f::from_points(
Point2f::new(crop_data[0], crop_data[2]),
Point2f::new(crop_data[1], crop_data[3]),
)
} else {
Bounds2f::from_points(Point2f::zero(), Point2f::new(1.0, 1.0))
};
let p_min = Point2i::new(
(full_resolution.x() as Float * crop.p_min.x()).ceil() as i32,
(full_resolution.y() as Float * crop.p_min.y()).ceil() as i32,
);
let p_max = Point2i::new(
(full_resolution.x() as Float * crop.p_max.x()).ceil() as i32,
(full_resolution.y() as Float * crop.p_max.y()).ceil() as i32,
);
let mut pixel_bounds = Bounds2i::from_points(p_min, p_max);
if pixel_bounds.is_empty() {
eprintln!("{}: Film crop window results in empty pixel bounds.", loc);
}
let rad = filter.radius();
let expansion = Point2i::new(rad.x().ceil() as i32, rad.y().ceil() as i32);
pixel_bounds = pixel_bounds.expand(expansion);
let diagonal_mm = params.get_one_float("diagonal", 35.0);
let filename = params.get_one_string("filename", "pbrt.exr");
Self {
full_resolution,
pixel_bounds,
filter,
diagonal: diagonal_mm * 0.001,
sensor,
}
}
}
pub trait FilmHost {
fn write_image(&self, metadata: &ImageMetadata, splat_scale: Float) {
let image = self.get_image(metadata, splat_scale);
image
.write(self.get_filename(), metadata)
.expect("Something")
}
fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image {
let write_fp16 = true;
let format = if write_fp16 {
PixelFormat::F16
} else {
PixelFormat::F32
};
let channel_names = &["R", "G", "B"];
let pixel_bounds = self.base().pixel_bounds;
let resolution = Point2i::from(pixel_bounds.diagonal());
let n_clamped = Arc::new(AtomicUsize::new(0));
let processed_rows: Vec<Vec<Float>> = (pixel_bounds.p_min.y()..pixel_bounds.p_max.y())
.into_par_iter()
.map(|y| {
let n_clamped = Arc::clone(&n_clamped);
let mut row_data = Vec::with_capacity(resolution.x() as usize * 3);
for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() {
let p = Point2i::new(x, y);
let mut rgb = self.get_pixel_rgb(p, Some(splat_scale));
let mut was_clamped = false;
if write_fp16 {
if rgb.r > 65504.0 {
rgb.r = 65504.0;
was_clamped = true;
}
if rgb.g > 65504.0 {
rgb.g = 65504.0;
was_clamped = true;
}
if rgb.b > 65504.0 {
rgb.b = 65504.0;
was_clamped = true;
}
}
if was_clamped {
n_clamped.fetch_add(1, Ordering::SeqCst);
}
row_data.push(rgb.r);
row_data.push(rgb.g);
row_data.push(rgb.b);
}
row_data
})
.collect();
let mut image = Image::new(format, resolution, channel_names, SRGB);
let rgb_desc = ImageChannelDesc::new(&[0, 1, 2]);
for (iy, row_data) in processed_rows.into_iter().enumerate() {
for (ix, rgb_chunk) in row_data.chunks_exact(3).enumerate() {
let p_offset = Point2i::new(ix as i32, iy as i32);
let values = ImageChannelValues::from(rgb_chunk);
image.set_channels(p_offset, &rgb_desc, &values);
}
}
let clamped_count = n_clamped.load(Ordering::SeqCst);
if clamped_count > 0 {
println!(
"{} pixel values clamped to maximum fp16 value.",
clamped_count
);
}
// self.base().pixel_bounds = pixel_bounds;
// self.base().full_resolution = resolution;
// self.colorspace = colorspace;
image
}
fn get_filename(&self) -> &str;
}
pub trait FilmFactory {
fn create(
name: &str,
params: &ParameterDictionary,
exposure_time: Float,
filter: Filter,
_camera_transform: Option<CameraTransform>,
loc: &FileLoc,
) -> Result<Self, String>;
}
impl FilmFactory for Film {
fn create(
name: &str,
params: &ParameterDictionary,
exposure_time: Float,
filter: Filter,
camera_transform: Option<CameraTransform>,
loc: &FileLoc,
) -> Result<Self, String> {
match name {
"rgb" => {
let colorspace = params.color_space.as_ref().unwrap();
let max_component_value =
params.get_one_float("maxcomponentvalue", Float::INFINITY);
let write_fp16 = params.get_one_bool("savefp16", true);
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
let film_base = FilmBase::create(params, filter, Some(sensor), loc);
Ok(RGBFilm::new(
film_base,
&colorspace,
max_component_value,
write_fp16,
))
}
"gbuffer" => {
let colorspace = params.color_space.as_ref().unwrap();
let max_component_value =
params.get_one_float("maxcomponentvalue", Float::INFINITY);
let write_fp16 = params.get_one_bool("savefp16", true);
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
let film_base = FilmBase::create(params, filter, Some(sensor), loc);
let filename = params.get_one_string("filename", "pbrt.exr");
if Path::new(&ilename).extension() != Some("exr".as_ref()) {
return Err(format!(
"{}: EXR is the only format supported by GBufferFilm",
loc
)
.into());
}
let coords_system = params.get_one_string("coordinatesystem", "camera");
let mut apply_inverse = false;
let camera_transform = camera_transform
.ok_or_else(|| "GBufferFilm requires a camera_transform".to_string())?;
let output_from_render = if coords_system == "camera" {
apply_inverse = true;
camera_transform.render_from_camera
} else if coords_system == "world" {
AnimatedTransform::from_transform(&camera_transform.world_from_render)
} else {
return Err(format!(
"{}: unknown coordinate system for GBufferFilm. (Expecting camera
or world",
loc
)
.into());
};
Ok(GBufferFilm::new(
&film_base,
&output_from_render,
apply_inverse,
colorspace,
max_component_value,
write_fp16,
))
}
"spectral" => {
let colorspace = params.color_space.as_ref().unwrap();
let max_component_value =
params.get_one_float("maxcomponentvalue", Float::INFINITY);
let write_fp16 = params.get_one_bool("savefp16", true);
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
let film_base = FilmBase::create(params, filter, Some(sensor), loc);
let filename = params.get_one_string("filename", "pbrt.exr");
if Path::new(&filename).extension() != Some("exr".as_ref()) {
return Err(format!(
"{}: EXR is the only format supported by GBufferFilm",
loc
)
.into());
}
let n_buckets = params.get_one_int("nbuckets", 16) as usize;
let lambda_min = params.get_one_float("lambdamin", LAMBDA_MIN as Float);
let lambda_max = params.get_one_float("lambdamin", LAMBDA_MAX as Float);
if lambda_min < LAMBDA_MIN as Float && lambda_max > LAMBDA_MAX as Float {
return Err(format!(
"{}: PBRT must be recompiled with different values of LAMBDA_MIN and LAMBDA_MAX",
loc
));
}
Ok(SpectralFilm::new(
&film_base,
lambda_min,
lambda_max,
n_buckets,
colorspace,
max_component_value,
write_fp16,
))
}
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
}
}
}

48
src/core/filter.rs Normal file
View file

@ -0,0 +1,48 @@
use shared::filter::Filter;
use shared::filters::*;
pub trait FilterFactory {
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Filter, String>;
}
impl FilterFactory for Filter {
fn create(name: &str, params: ParameterDictionary, loc: &FileLoc) -> Result<Self, String> {
match name {
"box" => {
let xw = params.get_one_float("xradius", 0.5);
let yw = params.get_one_float("yradius", 0.5);
let filter = BoxFilter::new(Vector2f::new(xw, yw));
Ok(Filter::Box(filter))
}
"gaussian" => {
let xw = params.get_one_float("xradius", 1.5);
let yw = params.get_one_float("yradius", 1.5);
let sigma = params.get_one_float("sigma", 0.5);
let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma);
Ok(Filter::Gaussian(filter))
}
"mitchell" => {
let xw = params.get_one_float("xradius", 2.);
let yw = params.get_one_float("yradius", 2.);
let b = params.get_one_float("B", 1. / 3.);
let c = params.get_one_float("C", 1. / 3.);
let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c);
Ok(Filter::Mitchell(filter))
}
"sinc" => {
let xw = params.get_one_float("xradius", 4.);
let yw = params.get_one_float("yradius", 4.);
let tau = params.get_one_float("tau", 3.);
let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau);
Ok(Filter::LanczosSinc(filter))
}
"triangle" => {
let xw = params.get_one_float("xradius", 2.);
let yw = params.get_one_float("yradius", 2.);
let filter = TriangleFilter::new(Vector2f::new(xw, yw));
Ok(Filter::Triangle(filter))
}
_ => Err(format!("Film type '{}' unknown at {}", name, loc)),
}
}
}

View file

@ -1,2 +1,4 @@
pub mod film;
pub mod filter;
pub mod scene; pub mod scene;
pub mod texture; pub mod texture;

View file

@ -1,3 +1,4 @@
use crate::core::filter::FilterFactory;
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
use crate::utils::{normalize_utf8, resolve_filename}; use crate::utils::{normalize_utf8, resolve_filename};
use parking_lot::Mutex; use parking_lot::Mutex;

View file

@ -1,9 +1,9 @@
use crate::textures::*; use crate::textures::*;
use crate::utils::error::FileLoc; use crate::utils::FileLoc;
use crate::utils::mipmap::MIPMapFilterOptions; use crate::utils::mipmap::MIPMapFilterOptions;
use crate::utils::parameters::TextureParameterDictionary; use crate::utils::{FileLoc, TextureParameterDictionary};
use shared::textures::*;
use shared::utils::Transform; use shared::utils::Transform;
use shared::images::
use std::sync::{Arc, Mutex, OnceLock}; use std::sync::{Arc, Mutex, OnceLock};
#[enum_dispatch(FloatTextureTrait)] #[enum_dispatch(FloatTextureTrait)]
@ -23,6 +23,33 @@ pub enum FloatTexture {
Wrinkled(WrinkledTexture), Wrinkled(WrinkledTexture),
} }
impl FloatConstantTexture {
pub fn create(
name: &str,
render_from_texture: &Transform,
params: &TextureParameterDictionary,
loc: &FileLoc,
) -> Result<Self, String> {
Self::new(params.get_one_float("value", 1.0))
}
}
impl FloatBilerpTexture {
pub fn create(
name: &str,
render_from_texture: &Transform,
params: &TextureParameterDictionary,
loc: &FileLoc,
) -> Result<Self, String> {
let mapping = TextureMapping2D::create(params, render_from_texture, loc);
let v00 = params.get_one_float("v00", 0.);
let v01 = params.get_one_float("v01", 1.);
let v10 = params.get_one_float("v10", 0.);
let v11 = params.get_one_float("v11", 1.);
Self::new(mapping, v00, v01, v10, v11)
}
}
impl FloatTexture { impl FloatTexture {
pub fn create( pub fn create(
name: &str, name: &str,
@ -223,3 +250,41 @@ struct TexInfo {
wrap_mode: WrapMode, wrap_mode: WrapMode,
encoding: ColorEncoding, encoding: ColorEncoding,
} }
pub trait TextureEvaluator: Send + Sync {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float;
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum;
fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool;
}
#[derive(Copy, Clone, Default)]
pub struct UniversalTextureEvaluator;
impl TextureEvaluator for UniversalTextureEvaluator {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float {
tex.evaluate(ctx)
}
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
tex.evaluate(ctx, lambda)
}
fn can_evaluate(
&self,
_float_textures: &[&FloatTexture],
_spectrum_textures: &[&SpectrumTexture],
) -> bool {
true
}
}

View file

@ -1,48 +0,0 @@
use crate::core::texture::{
FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext, TextureMapping2D,
};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
#[derive(Debug, Clone)]
pub struct FloatBilerpTexture {
mapping: TextureMapping2D,
v00: Float,
v01: Float,
v10: Float,
v11: Float,
}
impl FloatBilerpTexture {
pub fn new(mapping: TextureMapping2D, v00: Float, v01: Float, v10: Float, v11: Float) -> Self {
Self {
mapping,
v00,
v01,
v10,
v11,
}
}
pub fn create(
render_from_texture: &Transform,
params: &TextureParameterDictionary,
loc: &FileLoc,
) -> Self {
let mapping = TextureMapping2D::create(params, render_from_texture, loc);
let v00 = params.get_one_float("v00", 0.);
let v01 = params.get_one_float("v01", 1.);
let v10 = params.get_one_float("v10", 0.);
let v11 = params.get_one_float("v11", 1.);
Self::new(mapping, v00, v01, v10, v11)
}
}
impl FloatTextureTrait for FloatBilerpTexture {}
#[derive(Clone, Debug)]
pub struct SpectrumBilerpTexture;
impl SpectrumTextureTrait for SpectrumBilerpTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
}

View file

@ -1,14 +0,0 @@
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
#[derive(Debug, Clone)]
pub struct FloatCheckerboardTexture;
impl FloatTextureTrait for FloatCheckerboardTexture {}
#[derive(Clone, Debug)]
pub struct SpectrumCheckerboardTexture;
impl SpectrumTextureTrait for SpectrumCheckerboardTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
}

View file

@ -1,60 +0,0 @@
use crate::Float;
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum};
#[derive(Debug, Clone)]
pub struct FloatConstantTexture {
value: Float,
}
impl FloatConstantTexture {
pub fn new(value: Float) -> Self {
Self { value }
}
pub fn create(
_render_from_texture: &Transform,
params: &TextureParameterDictionary,
_loc: &FileLoc,
) -> Self {
Self::new(params.get_one_float("value", 1.0))
}
}
impl FloatTextureTrait for FloatConstantTexture {
fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
self.value
}
}
#[derive(Clone, Debug)]
pub struct RGBConstantTexture;
impl SpectrumTextureTrait for RGBConstantTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct RGBReflectanceConstantTexture;
impl SpectrumTextureTrait for RGBReflectanceConstantTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
}
#[derive(Clone, Debug)]
pub struct SpectrumConstantTexture {
value: Spectrum,
}
impl SpectrumConstantTexture {
pub fn new(value: Spectrum) -> Self {
Self { value }
}
}
impl SpectrumTextureTrait for SpectrumConstantTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
self.value.sample(lambda)
}
}

View file

@ -1,11 +0,0 @@
#[derive(Clone, Debug)]
pub struct SpectrumDotsTexture;
impl SpectrumTextureTrait for SpectrumDotsTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
}
#[derive(Debug, Clone)]
pub struct FloatDotsTexture;
impl FloatTextureTrait for FloatDotsTexture {}

View file

@ -1,3 +0,0 @@
#[derive(Debug, Clone)]
pub struct FBmTexture;
impl FloatTextureTrait for FBmTexture {}

View file

@ -1,9 +1,10 @@
use crate::texture::ImageTextureBase; use crate::core::texture::ImageTextureBase;
use crate::utils::{FileLoc, TextureParameterDictionary};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SpectrumImageTexture { pub struct SpectrumImageTexture {
base: ImageTextureBase, pub base: ImageTextureBase,
spectrum_type: SpectrumType, pub spectrum_type: SpectrumType,
} }
impl SpectrumImageTexture { impl SpectrumImageTexture {

View file

@ -1,7 +0,0 @@
#[derive(Clone, Debug)]
pub struct MarbleTexture;
impl SpectrumTextureTrait for MarbleTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
}

View file

@ -1,3 +1,11 @@
use crate::core::texture::{
FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait,
};
use crate::utils::{FileLoc, TextureParameterDictionary};
use shared::core::geometry::Vector3f;
use shared::utils::Transform;
use std::sync::Arc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FloatMixTexture { pub struct FloatMixTexture {
tex1: Arc<FloatTexture>, tex1: Arc<FloatTexture>,

View file

@ -1,9 +1,155 @@
use crate::utils::{FileLoc, TextureParameterDictionary};
use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::spectra::ColorEncoding;
use shared::spectra::color::RGB;
use ptex::Cache;
use ptex_sys::ffi;
use std::sync::OnceLock;
static PTEX_CACHE: OnceLock<Cache> = OnceLock::new();
fn get_ptex_cache() -> &'static Cache {
PTEX_CACHE.get_or_init(|| {
let max_files = 100;
let max_mem = 1 << 32;
let premultiply = true;
Cache::new(max_files, max_mem, premultiply)
})
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FloatPtexTexture; pub struct PtexTextureBase {
impl FloatTextureTrait for FloatPtexTexture {} pub valid: bool,
pub filename: String,
pub encoding: ColorEncoding,
pub scale: Float,
}
impl PtexTextureBase {
pub fn new(filename: String, encoding: ColorEncoding, scale: Float) -> Self {
let cache = get_ptex_cache();
// Attempt to get the texture to verify it exists and is valid
let (valid, num_channels) = match cache.get(&filename) {
Ok(tex) => {
let nc = tex.num_channels();
(nc == 1 || nc == 3, nc)
}
Err(e) => {
log::error!("Ptex Error for {}: {}", filename, e);
(false, 0)
}
};
if !valid && num_channels != 0 {
log::error!(
"{}: only 1 and 3 channel ptex textures are supported",
filename
);
}
Self {
filename,
encoding,
scale,
valid,
}
}
pub fn sample_texture(ctx: &TextureEvalContext) -> Option<RGB> {
if !self.valid {
return None;
}
let mut result = [0.0; 3];
let nc = self.ptex_handle.eval(
&mut result,
ctx.face_index,
ctx.uv[0],
ctx.uv[1],
ctx.dudx,
ctx.dvdx,
ctx.dudy,
ctx.dvdy,
);
let cache = get_ptex_cache();
let texture = match cache.get(&self.filename) {
Ok(t) => t,
Err(e) => {
log::error!("Ptex cache lookup failed for {}: {}", self.filename, e);
return None;
}
};
let nc = texture.num_channels();
let mut result = [0.0f32; 4];
unsafe {
let opts = ffi::PtexFilter_Options {
filter: ffi::FilterType::f_bspline,
lerp: 1,
sharpness: 0.0,
noedgeblend: 0,
__structSize: std::mem::size_of::<ffi::PtexFilter_Options>() as i32,
};
// Get the raw filter from the low-level FFI
// Assuming your 'texture' can provide the raw C++ pointer
let filter_ptr = ffi::PtexFilter_getFilter(texture.as_ptr(), &opts);
if filter_ptr.is_null() {
return None;
}
// Evaluate
(*filter_ptr).eval(
result.as_mut_ptr(),
0, // first channel
texture.num_channels(),
ctx.face_index as i32,
ctx.uv[0],
ctx.uv[1],
ctx.dudx,
ctx.dvdx,
ctx.dudy,
ctx.dvdy,
);
// Crucial: Manually release the C++ object
(*filter_ptr).release();
}
let mut rgb = RGB::new(result[0], result[1], result[2]);
if self.encoding != ColorEncoding::Linear {
// Convert to 8-bit, process, and convert back
let r8 = (rgb.r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
let g8 = (rgb.g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
let b8 = (rgb.b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
rgb = self.encoding.to_linear_rgb([r8, g8, b8]);
}
Some(rgb * self.scale)
}
}
#[derive(Debug, Clone)]
pub struct FloatPtexTexture {
pub base: PtexTextureBase,
}
// impl FloatPtexTexture {
// pub fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
// let
//
// }
// }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SpectrumPtexTexture; pub struct SpectrumPtexTexture {
pub base: PtexTextureBase,
pub spectrum_type: SpectrumType,
}
impl SpectrumTextureTrait for SpectrumPtexTexture { impl SpectrumTextureTrait for SpectrumPtexTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!() todo!()

View file

@ -1,3 +1,11 @@
use crate::core::texture::{
FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait,
};
use crate::utils::{FileLoc, TextureParameterDictionary};
use shared::core::texture::TextureEvalContext;
use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::utils::Transform;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FloatScaledTexture { pub struct FloatScaledTexture {
tex: Arc<FloatTexture>, tex: Arc<FloatTexture>,
@ -18,14 +26,14 @@ impl FloatScaledTexture {
let mut scale = params.get_float_texture("scale", 1.); let mut scale = params.get_float_texture("scale", 1.);
for _ in 0..2 { for _ in 0..2 {
if let FloatTexture::FloatConstant(c_tex) = &*scale { if let FloatTexture::Constant(c_tex) = &*scale {
let cs = c_tex.value; let cs = c_tex.value;
if cs == 1.0 { if cs == 1.0 {
return (*tex).clone(); return (*tex).clone();
} else if let FloatTexture::FloatImage(img_tex) = &*tex { } else if let FloatTexture::Image(img_tex) = &*tex {
let mut image_copy = img_tex.clone(); let mut image_copy = img_tex.clone();
image_copy.base.multiply_scale(cs); image_copy.base.multiply_scale(cs);
return FloatTexture::FloatImage(image_copy).into(); return FloatTexture::Image(image_copy).into();
} }
} }
@ -48,8 +56,8 @@ impl FloatTextureTrait for FloatScaledTexture {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SpectrumScaledTexture { pub struct SpectrumScaledTexture {
tex: Box<SpectrumTexture>, tex: Arc<SpectrumTexture>,
scale: Box<FloatTexture>, scale: Arc<FloatTexture>,
} }
impl SpectrumTextureTrait for SpectrumScaledTexture { impl SpectrumTextureTrait for SpectrumScaledTexture {

View file

@ -1,3 +0,0 @@
#[derive(Debug, Clone)]
pub struct WindyTexture;
impl FloatTextureTrait for WindyTexture {}

View file

@ -1,3 +0,0 @@
#[derive(Debug, Clone)]
pub struct WrinkledTexture;
impl FloatTextureTrait for WrinkledTexture {}

View file

@ -5,5 +5,9 @@ pub mod mipmap;
pub mod parameters; pub mod parameters;
pub mod parser; pub mod parser;
pub use error::FileLoc;
pub use file::{read_float_file, resolve_filename}; pub use file::{read_float_file, resolve_filename};
pub use parameters::{
ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary,
};
pub use strings::*; pub use strings::*;