Big slog, separating GPU and CPU safe structs and constructors
This commit is contained in:
parent
2e9d3c7301
commit
cda63e42c5
52 changed files with 2253 additions and 1464 deletions
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
|
|
@ -14,6 +14,8 @@ num-integer = "0.1.46"
|
|||
num-traits = "0.2.19"
|
||||
once_cell = "1.21.3"
|
||||
smallvec = "1.15.1"
|
||||
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
|
||||
|
||||
[features]
|
||||
cuda = []
|
||||
use_f64 = []
|
||||
cuda = ["cuda_std"]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,7 @@
|
|||
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
||||
use crate::core::pbrt::Float;
|
||||
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::parameters::ParameterDictionary;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
|
|
@ -19,6 +17,7 @@ pub struct FilterSampler {
|
|||
f: Array2D<Float>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl FilterSampler {
|
||||
pub fn new<F>(radius: Vector2f, func: F) -> Self
|
||||
where
|
||||
|
|
@ -45,27 +44,21 @@ impl FilterSampler {
|
|||
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
||||
Self { domain, f, distrib }
|
||||
}
|
||||
}
|
||||
|
||||
/// Samples the filter's distribution.
|
||||
impl FilterSampler {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
let (p, pdf, pi) = self.distrib.sample(u);
|
||||
|
||||
if pdf == 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 }
|
||||
}
|
||||
}
|
||||
|
||||
#[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)]
|
||||
pub enum Filter {
|
||||
Box(BoxFilter),
|
||||
|
|
@ -76,310 +69,42 @@ pub enum Filter {
|
|||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn create(name: &str, params: ParameterDictionary, loc: &FileLoc) -> Result<Self, String> {
|
||||
match name {
|
||||
"box" => {
|
||||
let filter = BoxFilter::create(¶ms, loc);
|
||||
Ok(Filter::Box(filter))
|
||||
}
|
||||
"gaussian" => {
|
||||
let filter = GaussianFilter::create(¶ms, loc);
|
||||
Ok(Filter::Gaussian(filter))
|
||||
}
|
||||
"mitchell" => {
|
||||
let filter = MitchellFilter::create(¶ms, loc);
|
||||
Ok(Filter::Mitchell(filter))
|
||||
}
|
||||
"sinc" => {
|
||||
let filter = LanczosSincFilter::create(¶ms, loc);
|
||||
Ok(Filter::LanczosSinc(filter))
|
||||
}
|
||||
"triangle" => {
|
||||
let filter = TriangleFilter::create(¶ms, 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))
|
||||
pub fn radius(&self) -> Vector2f {
|
||||
match self {
|
||||
Filter::Box(f) => f.radius(),
|
||||
Filter::Gaussian(f) => f.radius(),
|
||||
Filter::Mitchell(f) => f.radius(),
|
||||
Filter::LanczosSinc(f) => f.radius(),
|
||||
Filter::Triangle(f) => f.radius(),
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn evaluate(&self, p: Point2f) -> Float {
|
||||
match self {
|
||||
Filter::Box(f) => f.evaluate(p),
|
||||
Filter::Gaussian(f) => f.evaluate(p),
|
||||
Filter::Mitchell(f) => f.evaluate(p),
|
||||
Filter::LanczosSinc(f) => f.evaluate(p),
|
||||
Filter::Triangle(f) => f.evaluate(p),
|
||||
}
|
||||
}
|
||||
|
||||
fn integral(&self) -> Float {
|
||||
(2.0 * self.radius.x()) * (2.0 * self.radius.y())
|
||||
}
|
||||
|
||||
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 }
|
||||
pub fn integral(&self) -> Float {
|
||||
match self {
|
||||
Filter::Box(f) => f.integral(),
|
||||
Filter::Gaussian(f) => f.integral(),
|
||||
Filter::Mitchell(f) => f.integral(),
|
||||
Filter::LanczosSinc(f) => f.integral(),
|
||||
Filter::Triangle(f) => f.integral(),
|
||||
}
|
||||
}
|
||||
|
||||
#[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 sample(&self, u: Point2f) -> FilterSample {
|
||||
match self {
|
||||
Filter::Box(f) => f.sample(u),
|
||||
Filter::Gaussian(f) => f.sample(u),
|
||||
Filter::Mitchell(f) => f.sample(u),
|
||||
Filter::LanczosSinc(f) => f.sample(u),
|
||||
Filter::Triangle(f) => f.sample(u),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
|
||||
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);
|
||||
Self::new(Vector2f::new(xw, yw), sigma)
|
||||
}
|
||||
}
|
||||
|
||||
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 create(params: &ParameterDictionary, _loc: &FileLoc) -> Self {
|
||||
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.);
|
||||
Self::new(Vector2f::new(xw, yw), b, c)
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::core::pbrt::{Float, PI};
|
|||
use crate::images::Image;
|
||||
use crate::spectra::{
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum,
|
||||
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
|
||||
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
||||
};
|
||||
use crate::utils::containers::InternCache;
|
||||
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use crate::core::texture::{
|
|||
FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator,
|
||||
};
|
||||
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::math::clamp;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::core::geometry::{
|
|||
use crate::core::pbrt::{Float, INV_4_PI, PI};
|
||||
use crate::spectra::{
|
||||
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::math::{clamp, square};
|
||||
|
|
|
|||
|
|
@ -8,29 +8,22 @@ use crate::images::WrapMode;
|
|||
use crate::spectra::color::ColorEncoding;
|
||||
use crate::spectra::{
|
||||
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths, Spectrum, SpectrumProvider,
|
||||
SampledWavelengths, Spectrum, SpectrumTrait,
|
||||
};
|
||||
use crate::textures::*;
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::math::square;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::path::Path;
|
||||
|
||||
struct TexCoord2D {
|
||||
st: Point2f,
|
||||
dsdx: Float,
|
||||
dsdy: Float,
|
||||
dtdx: Float,
|
||||
dtdy: Float,
|
||||
#[repr(C)]
|
||||
pub struct TexCoord2D {
|
||||
pub st: Point2f,
|
||||
pub dsdx: Float,
|
||||
pub dsdy: Float,
|
||||
pub dtdx: Float,
|
||||
pub dtdy: Float,
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
trait TextureMapping2DTrait {
|
||||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D;
|
||||
}
|
||||
|
||||
#[enum_dispatch(TextureMapping2DTrait)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TextureMapping2D {
|
||||
UV(UVMapping),
|
||||
|
|
@ -39,6 +32,17 @@ pub enum TextureMapping2D {
|
|||
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)]
|
||||
pub struct UVMapping {
|
||||
su: Float,
|
||||
|
|
@ -47,12 +51,6 @@ pub struct UVMapping {
|
|||
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 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
@ -64,8 +62,12 @@ impl Default for UVMapping {
|
|||
}
|
||||
}
|
||||
|
||||
impl TextureMapping2DTrait for UVMapping {
|
||||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
|
||||
impl UVMapping {
|
||||
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 dsdy = self.su * ctx.dudy;
|
||||
let dtdx = self.sv * ctx.dvdx;
|
||||
|
|
@ -83,19 +85,17 @@ impl TextureMapping2DTrait for UVMapping {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SphericalMapping {
|
||||
texture_from_render: TransformGeneric<Float>,
|
||||
texture_from_render: Transform,
|
||||
}
|
||||
|
||||
impl SphericalMapping {
|
||||
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
|
||||
pub fn new(texture_from_render: &Transform) -> Self {
|
||||
Self {
|
||||
texture_from_render: *texture_from_render,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureMapping2DTrait for SphericalMapping {
|
||||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
|
||||
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
|
||||
let pt = self.texture_from_render.apply_to_point(ctx.p);
|
||||
let x2y2 = square(pt.x()) + square(pt.y());
|
||||
let sqrtx2y2 = x2y2.sqrt();
|
||||
|
|
@ -126,19 +126,17 @@ impl TextureMapping2DTrait for SphericalMapping {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CylindricalMapping {
|
||||
texture_from_render: TransformGeneric<Float>,
|
||||
texture_from_render: Transform,
|
||||
}
|
||||
|
||||
impl CylindricalMapping {
|
||||
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
|
||||
pub fn new(texture_from_render: &Transform) -> Self {
|
||||
Self {
|
||||
texture_from_render: *texture_from_render,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureMapping2DTrait for CylindricalMapping {
|
||||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
|
||||
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
|
||||
let pt = self.texture_from_render.apply_to_point(ctx.p);
|
||||
let x2y2 = square(pt.x()) + square(pt.y());
|
||||
let dsdp = Vector3f::new(-pt.y(), pt.x(), 0.) / (2. * PI * x2y2);
|
||||
|
|
@ -162,7 +160,7 @@ impl TextureMapping2DTrait for CylindricalMapping {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlanarMapping {
|
||||
texture_from_render: TransformGeneric<Float>,
|
||||
texture_from_render: Transform,
|
||||
vs: Vector3f,
|
||||
vt: Vector3f,
|
||||
ds: Float,
|
||||
|
|
@ -171,7 +169,7 @@ pub struct PlanarMapping {
|
|||
|
||||
impl PlanarMapping {
|
||||
pub fn new(
|
||||
texture_from_render: &TransformGeneric<Float>,
|
||||
texture_from_render: &Transform,
|
||||
vs: Vector3f,
|
||||
vt: Vector3f,
|
||||
ds: Float,
|
||||
|
|
@ -185,10 +183,8 @@ impl PlanarMapping {
|
|||
dt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureMapping2DTrait for PlanarMapping {
|
||||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
|
||||
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord2D {
|
||||
let vec: Vector3f = self.texture_from_render.apply_to_point(ctx.p).into();
|
||||
let dpdx = self.texture_from_render.apply_to_vector(ctx.dpdx);
|
||||
let dpdy = self.texture_from_render.apply_to_vector(ctx.dpdy);
|
||||
|
|
@ -208,9 +204,9 @@ impl TextureMapping2DTrait for PlanarMapping {
|
|||
}
|
||||
|
||||
pub struct TexCoord3D {
|
||||
p: Point3f,
|
||||
dpdx: Vector3f,
|
||||
dpdy: Vector3f,
|
||||
pub p: Point3f,
|
||||
pub dpdx: Vector3f,
|
||||
pub dpdy: Vector3f,
|
||||
}
|
||||
|
||||
pub trait TextureMapping3DTrait {
|
||||
|
|
@ -218,26 +214,31 @@ pub trait TextureMapping3DTrait {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[enum_dispatch(TextureMapping3DTrait)]
|
||||
pub enum TextureMapping3D {
|
||||
PointTransform(PointTransformMapping),
|
||||
}
|
||||
|
||||
impl TextureMapping3D {
|
||||
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D {
|
||||
match self {
|
||||
TextureMapping3D::PointTransform(t) => t.map(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PointTransformMapping {
|
||||
texture_from_render: TransformGeneric<Float>,
|
||||
texture_from_render: Transform,
|
||||
}
|
||||
|
||||
impl PointTransformMapping {
|
||||
pub fn new(texture_from_render: &TransformGeneric<Float>) -> Self {
|
||||
pub fn new(texture_from_render: &Transform) -> Self {
|
||||
Self {
|
||||
texture_from_render: *texture_from_render,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureMapping3DTrait for PointTransformMapping {
|
||||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D {
|
||||
pub fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D {
|
||||
TexCoord3D {
|
||||
p: self.texture_from_render.apply_to_point(ctx.p),
|
||||
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)]
|
||||
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),
|
||||
Image(GPUFloatImageTexture),
|
||||
Mix(GPUFloatMixTexture),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[enum_dispatch(SpectrumTextureTrait)]
|
||||
pub enum SpectrumTexture {
|
||||
Image(GPUSpectrumImageTexture),
|
||||
Ptex(GPUSpectrumPtexTexture),
|
||||
Mix(GPUSpectrumMixTexture),
|
||||
impl GPUFloatTexture {
|
||||
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
match self {
|
||||
GPUFloatTexture::Constant(t) => t.evaluate(ctx),
|
||||
GPUFloatTexture::DirectionMix(t) => t.evaluate(ctx),
|
||||
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)]
|
||||
|
|
@ -359,40 +365,34 @@ pub enum SpectrumType {
|
|||
Albedo,
|
||||
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)]
|
||||
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
|
||||
impl GPUSpectrumTexture {
|
||||
pub fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> Float {
|
||||
match self {
|
||||
GPUSpectrumTexture::Constant(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Bilerp(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Checkerboard(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Marble(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::DirectionMix(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Dots(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Scaled(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Ptex(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Image(t) => t.evaluate(ctx, lambda),
|
||||
GPUSpectrumTexture::Mix(t) => t.evaluate(ctx, lambda),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
shared/src/filters/boxf.rs
Normal file
38
shared/src/filters/boxf.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
49
shared/src/filters/gaussian.rs
Normal file
49
shared/src/filters/gaussian.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
56
shared/src/filters/lanczos.rs
Normal file
56
shared/src/filters/lanczos.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
67
shared/src/filters/mitchell.rs
Normal file
67
shared/src/filters/mitchell.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
5
shared/src/filters/mod.rs
Normal file
5
shared/src/filters/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod boxf;
|
||||
pub mod gaussian;
|
||||
pub mod lanczos;
|
||||
pub mod mitchell;
|
||||
pub mod triangle;
|
||||
30
shared/src/filters/triangle.rs
Normal file
30
shared/src/filters/triangle.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
mod cameras;
|
||||
mod core;
|
||||
mod data;
|
||||
mod filters;
|
||||
mod images;
|
||||
mod integrators;
|
||||
mod lights;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
|||
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::pbrt::Float;
|
||||
use crate::spectra::{BlackbodySpectrum, SpectrumProvider};
|
||||
use crate::spectra::{BlackbodySpectrum, SpectrumTrait};
|
||||
use crate::utils::math::square;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ pub mod sampled;
|
|||
pub mod simple;
|
||||
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::utils::file::read_float_file;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
pub use color::{RGB, XYZ};
|
||||
pub use color::{ColorEncoding, RGB, RGBSigmoidPolynomial, XYZ};
|
||||
pub use colorspace::RGBColorSpace;
|
||||
pub use data::*;
|
||||
pub use rgb::*;
|
||||
|
|
@ -19,32 +18,33 @@ pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
|||
pub use simple::*;
|
||||
//
|
||||
#[enum_dispatch]
|
||||
pub trait SpectrumProvider: Copy {
|
||||
pub trait SpectrumTrait: Copy {
|
||||
fn evaluate(&self, lambda: Float) -> Float;
|
||||
fn max_value(&self) -> Float;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
impl SpectrumProvider for std::sync::Arc<DenselySampledSpectrum> {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
(**self).evaluate(lambda)
|
||||
}
|
||||
fn max_value(&self) -> Float {
|
||||
(**self).max_value()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "spirv")] // or target_os = "cuda"
|
||||
impl SpectrumProvider for u32 {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
// Here you would call a global function that accesses
|
||||
// a static buffer of spectra data
|
||||
crate::gpu::lookup_global_spectrum(*self, lambda)
|
||||
}
|
||||
fn max_value(&self) -> Float {
|
||||
crate::gpu::lookup_global_spectrum_max(*self)
|
||||
}
|
||||
}
|
||||
// #[cfg(not(target_arch = "spirv"))]
|
||||
// impl SpectrumTrait for std::sync::Arc<DenselySampledSpectrum> {
|
||||
// fn evaluate(&self, lambda: Float) -> Float {
|
||||
// (**self).evaluate(lambda)
|
||||
// }
|
||||
// fn max_value(&self) -> Float {
|
||||
// (**self).max_value()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[cfg(target_arch = "spirv")] // or target_os = "cuda"
|
||||
// impl SpectrumTrait for u32 {
|
||||
// fn evaluate(&self, lambda: Float) -> Float {
|
||||
// // Here you would call a global function that accesses
|
||||
// // a static buffer of spectra data
|
||||
// crate::gpu::lookup_global_spectrum(*self, lambda)
|
||||
// }
|
||||
// fn max_value(&self) -> Float {
|
||||
// crate::gpu::lookup_global_spectrum_max(*self)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
#[enum_dispatch(SpectrumTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::{
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace,
|
||||
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumProvider, XYZ,
|
||||
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumTrait, XYZ,
|
||||
};
|
||||
|
||||
use crate::Float;
|
||||
|
|
@ -26,7 +26,7 @@ impl RGBAlbedoSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpectrumProvider for RGBAlbedoSpectrum {
|
||||
impl SpectrumTrait for RGBAlbedoSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
self.rsp.evaluate(lambda)
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ impl UnboundedRGBSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpectrumProvider for UnboundedRGBSpectrum {
|
||||
impl SpectrumTrait for UnboundedRGBSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
self.scale * self.rsp.evaluate(lambda)
|
||||
}
|
||||
|
|
@ -69,10 +69,10 @@ impl SpectrumProvider for UnboundedRGBSpectrum {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RGBIlluminantSpectrum<P: SpectrumProvider> {
|
||||
pub struct RGBIlluminantSpectrum {
|
||||
scale: Float,
|
||||
rsp: RGBSigmoidPolynomial,
|
||||
illuminant: P,
|
||||
illuminant: DenselySampledSpectrum,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
match &self.illuminant {
|
||||
Some(illuminant) => {
|
||||
|
|
@ -161,7 +161,7 @@ impl RGBUnboundedSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpectrumProvider for RGBUnboundedSpectrum {
|
||||
impl SpectrumTrait for RGBUnboundedSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
self.scale * self.rsp.evaluate(lambda)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
|||
use crate::core::cie::*;
|
||||
use crate::core::pbrt::Float;
|
||||
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 std::cmp::Ordering;
|
||||
|
|
@ -21,7 +21,7 @@ impl ConstantSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpectrumProvider for ConstantSpectrum {
|
||||
impl SpectrumTrait for ConstantSpectrum {
|
||||
fn evaluate(&self, _lambda: Float) -> Float {
|
||||
self.c
|
||||
}
|
||||
|
|
@ -165,7 +165,7 @@ impl Hash for DenselySampledSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpectrumProvider for DenselySampledSpectrum {
|
||||
impl SpectrumTrait for DenselySampledSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
let offset = (lambda.round() as i32) - self.lambda_min;
|
||||
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 {
|
||||
if self.lambdas.is_empty() {
|
||||
return 0.0;
|
||||
|
|
@ -321,7 +321,7 @@ impl BlackbodySpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpectrumProvider for BlackbodySpectrum {
|
||||
impl SpectrumTrait for BlackbodySpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
Self::planck_law(lambda, self.temperature) * self.normalization_factor
|
||||
}
|
||||
|
|
|
|||
85
shared/src/textures/bilerp.rs
Normal file
85
shared/src/textures/bilerp.rs
Normal 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),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
29
shared/src/textures/checkerboard.rs
Normal file
29
shared/src/textures/checkerboard.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
37
shared/src/textures/constant.rs
Normal file
37
shared/src/textures/constant.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
19
shared/src/textures/dots.rs
Normal file
19
shared/src/textures/dots.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
15
shared/src/textures/fbm.rs
Normal file
15
shared/src/textures/fbm.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
use crate::{
|
||||
core::texture::{
|
||||
FloatTextureTrait, ImageTextureBase, SpectrumTextureTrait, SpectrumType, TextureMapping2D,
|
||||
},
|
||||
spectra::RGBColorSpace,
|
||||
};
|
||||
use crate::Float;
|
||||
use crate::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
|
||||
use crate::spectra::RGBColorSpace;
|
||||
|
||||
/* GPU heavy code, dont know if this will ever work the way Im doing things.
|
||||
* Leaving it here isolated, for careful handling */
|
||||
|
|
@ -15,13 +12,59 @@ pub struct GPUSpectrumImageTexture {
|
|||
pub tex_obj: u64,
|
||||
pub scale: Float,
|
||||
pub invert: bool,
|
||||
pub is_single_channel: bool,
|
||||
pub color_space: RGBColorSpace,
|
||||
pub spectrum_type: SpectrumType,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for GPUSpectrumImageTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
impl GPUSpectrumImageTexture {
|
||||
pub fn evaluate(
|
||||
&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,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
56
shared/src/textures/marble.rs
Normal file
56
shared/src/textures/marble.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,23 @@
|
|||
pub mod bilerp;
|
||||
pub mod checkerboard;
|
||||
pub mod constant;
|
||||
pub mod dots;
|
||||
pub mod fbm;
|
||||
pub mod image;
|
||||
pub mod marble;
|
||||
pub mod mix;
|
||||
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::*;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,55 @@
|
|||
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::Float;
|
||||
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
|
||||
*/
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GPUSpectrumPtexTexture;
|
||||
impl SpectrumTextureTrait for GPUSpectrumPtexTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GPUFloatPtexTexture {
|
||||
pub face_values: Vec<Float>,
|
||||
}
|
||||
|
||||
impl GPUFloatPtexTexture {
|
||||
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
self.face_values[ctx.face_index]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GPUFloatPtexTexture;
|
||||
impl FloatTextureTrait for GPUFloatPtexTexture {}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GPUSpectrumPtexTexture {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
shared/src/textures/windy.rs
Normal file
16
shared/src/textures/windy.rs
Normal 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
|
||||
}
|
||||
}
|
||||
16
shared/src/textures/wrinkled.rs
Normal file
16
shared/src/textures/wrinkled.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,134 +20,161 @@ use crate::core::geometry::{
|
|||
// {
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Array2D<T> {
|
||||
pub values: Vec<T>,
|
||||
pub values: *mut T,
|
||||
pub extent: Bounds2i,
|
||||
}
|
||||
|
||||
impl<T> Array2D<T> {
|
||||
pub fn new(extent: Bounds2i) -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
let size = extent.area() as usize;
|
||||
let mut values = Vec::with_capacity(size);
|
||||
values.resize_with(size, T::default);
|
||||
Self { values, extent }
|
||||
#[inline]
|
||||
pub fn x_size(&self) -> usize {
|
||||
(self.extent.p_max.x() - self.extent.p_min.x()) as usize
|
||||
}
|
||||
|
||||
pub fn new_with_dims(nx: usize, ny: usize) -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
Self::new(Bounds2i::from_points(
|
||||
Point2i::new(0, 0),
|
||||
Point2i::new(nx as i32, ny as i32),
|
||||
))
|
||||
#[inline]
|
||||
pub fn y_size(&self) -> usize {
|
||||
(self.extent.p_max.y() - self.extent.p_min.y()) as usize
|
||||
}
|
||||
|
||||
pub fn new_filled(width: usize, height: usize, value: T) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
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(
|
||||
Point2i::new(0, 0),
|
||||
Point2i::new(width as i32, height as i32),
|
||||
);
|
||||
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> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, mut p: Point2i) -> &Self::Output {
|
||||
p -= Vector2i::from(self.extent.p_min);
|
||||
let width = self.extent.p_max.x() - self.extent.p_min.x();
|
||||
let idx = (p.x() + width * p.y()) as usize;
|
||||
&self.values[idx]
|
||||
unsafe { self.get(pos.0, pos.1) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
impl<T> IndexMut<Point2i> for Array2D<T> {
|
||||
fn index_mut(&mut self, mut p: Point2i) -> &mut Self::Output {
|
||||
p -= Vector2i::from(self.extent.p_min);
|
||||
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)]
|
||||
unsafe { self.get_mut(pos.0, pos.1) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
impl<T> Index<(i32, i32)> for Array2D<T> {
|
||||
type Output = T;
|
||||
fn index(&self, index: (i32, i32)) -> &Self::Output {
|
||||
self.index(Point2i::new(index.0, index.1))
|
||||
fn index(&self, pos: (i32, i32)) -> &Self::Output {
|
||||
unsafe { self.get(pos.0, pos.1) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "cuda"))]
|
||||
impl<T> IndexMut<(i32, i32)> for Array2D<T> {
|
||||
fn index_mut(&mut self, index: (i32, i32)) -> &mut Self::Output {
|
||||
self.index_mut(Point2i::new(index.0, index.1))
|
||||
fn index_mut(&mut self, pos: (i32, i32)) -> &mut Self::Output {
|
||||
unsafe { self.get_mut(pos.0, pos.1) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ pub mod hash;
|
|||
pub mod interval;
|
||||
pub mod math;
|
||||
pub mod mesh;
|
||||
pub mod noise;
|
||||
pub mod quaternion;
|
||||
pub mod rng;
|
||||
pub mod sampling;
|
||||
|
|
@ -30,36 +31,49 @@ where
|
|||
i
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct AtomicFloat {
|
||||
bits: AtomicU64,
|
||||
value: f64,
|
||||
}
|
||||
|
||||
impl AtomicFloat {
|
||||
pub fn new(value: f64) -> Self {
|
||||
Self {
|
||||
bits: AtomicU64::new(value.to_bits()),
|
||||
}
|
||||
Self { value }
|
||||
}
|
||||
|
||||
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) })
|
||||
}
|
||||
|
||||
pub fn store(&self, value: f64) {
|
||||
self.bits.store(value.to_bits(), Ordering::Relaxed);
|
||||
#[cfg(target_os = "cuda")]
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn add(&self, value: f64) {
|
||||
let mut current_bits = self.bits.load(Ordering::Relaxed);
|
||||
pub fn add(&self, v: f64) {
|
||||
let ptr = &self.value as *const f64 as *mut f64;
|
||||
|
||||
#[cfg(target_os = "cuda")]
|
||||
unsafe {
|
||||
cuda_std::intrinsics::atomic_add(ptr, v);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
unsafe {
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
let atomic_ptr = ptr as *const AtomicU64;
|
||||
let atomic = &*atomic_ptr;
|
||||
let mut current_bits = atomic.load(Ordering::Relaxed);
|
||||
loop {
|
||||
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(
|
||||
let new_val = current_val + v;
|
||||
match atomic.compare_exchange_weak(
|
||||
current_bits,
|
||||
new_bits,
|
||||
new_val.to_bits(),
|
||||
Ordering::Relaxed,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
|
|
@ -69,9 +83,4 @@ impl AtomicFloat {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AtomicFloat {
|
||||
fn default() -> Self {
|
||||
Self::new(0.0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
179
shared/src/utils/noise.rs
Normal file
179
shared/src/utils/noise.rs
Normal 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;
|
||||
}
|
||||
|
|
@ -700,64 +700,84 @@ impl VarianceEstimator {
|
|||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[repr(C)]
|
||||
pub struct PLSample {
|
||||
pub p: Point2f,
|
||||
pub pdf: Float,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PiecewiseConstant1D {
|
||||
pub func: Vec<Float>,
|
||||
pub cdf: Vec<Float>,
|
||||
pub func: *mut Float,
|
||||
pub cdf: *mut Float,
|
||||
pub min: Float,
|
||||
pub max: Float,
|
||||
pub n: usize,
|
||||
pub func_integral: Float,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
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 {
|
||||
assert!(max > min);
|
||||
let n = f.len();
|
||||
let mut func = Vec::with_capacity(n);
|
||||
for &val in f {
|
||||
func.push(val.abs());
|
||||
}
|
||||
let mut func_vec = f.to_vec();
|
||||
let mut cdf_vec = vec![0.0; n + 1];
|
||||
|
||||
let mut cdf = vec![0.; n + 1];
|
||||
cdf_vec[0] = 0.0;
|
||||
for i in 1..=n {
|
||||
debug_assert!(func[i - 1] >= 0.);
|
||||
cdf[i] = cdf[i - 1] + func[i - 1] * (max - min) / n as Float;
|
||||
cdf_vec[i] = cdf_vec[i - 1] + func_vec[i - 1] / 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];
|
||||
if func_integral == 0. {
|
||||
let n_float = n as Float;
|
||||
cdf.iter_mut()
|
||||
.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);
|
||||
}
|
||||
let func = func_vec.as_mut_ptr();
|
||||
let cdf = cdf_vec.as_mut_ptr();
|
||||
std::mem::forget(func_vec);
|
||||
std::mem::forget(cdf_vec);
|
||||
|
||||
Self {
|
||||
func,
|
||||
cdf,
|
||||
func_integral,
|
||||
min,
|
||||
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 {
|
||||
self.func_integral
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.func.len()
|
||||
self.n
|
||||
}
|
||||
|
||||
pub fn sample(&self, u: Float) -> (Float, Float, usize) {
|
||||
|
|
@ -776,37 +796,46 @@ impl PiecewiseConstant1D {
|
|||
(value, pdf_val, o)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PiecewiseConstant2D {
|
||||
pub p_conditional_v: Vec<PiecewiseConstant1D>,
|
||||
pub p_marginal: PiecewiseConstant1D,
|
||||
pub domain: Bounds2f,
|
||||
pub p_conditional_v: *mut PiecewiseConstant1D,
|
||||
pub p_marginal: PiecewiseConstant1D,
|
||||
pub n_conditionals: usize,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl PiecewiseConstant2D {
|
||||
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 {
|
||||
let start = v * nu;
|
||||
let end = start + nu;
|
||||
p_conditional_v.push(PiecewiseConstant1D::new_with_bounds(
|
||||
&data.as_slice()[start..end],
|
||||
let row = unsafe { core::slice::from_raw_parts(data.values.add(v * nu), nu) };
|
||||
conditionals.push(PiecewiseConstant1D::new_with_bounds(
|
||||
row,
|
||||
domain.p_min.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(
|
||||
&marginal_func,
|
||||
&marginal_funcs,
|
||||
domain.p_min.y(),
|
||||
domain.p_max.y(),
|
||||
);
|
||||
|
||||
let p_conditional_v = conditionals.as_mut_ptr();
|
||||
std::mem::forget(conditionals);
|
||||
|
||||
Self {
|
||||
p_conditional_v,
|
||||
p_marginal,
|
||||
domain,
|
||||
n_conditionals: nv,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -817,14 +846,28 @@ impl PiecewiseConstant2D {
|
|||
pub fn new_with_data(data: &Array2D<Float>) -> Self {
|
||||
let nx = data.x_size();
|
||||
let ny = data.y_size();
|
||||
Self::new(
|
||||
data,
|
||||
nx,
|
||||
ny,
|
||||
Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)),
|
||||
)
|
||||
let domain = Bounds2f::new(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
|
||||
|
||||
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 {
|
||||
Point2i::new(
|
||||
self.p_conditional_v[0].size() as i32,
|
||||
|
|
|
|||
349
src/core/film.rs
Normal file
349
src/core/film.rs
Normal 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
48
src/core/filter.rs
Normal 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
pub mod film;
|
||||
pub mod filter;
|
||||
pub mod scene;
|
||||
pub mod texture;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::core::filter::FilterFactory;
|
||||
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
|
||||
use crate::utils::{normalize_utf8, resolve_filename};
|
||||
use parking_lot::Mutex;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::textures::*;
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::FileLoc;
|
||||
use crate::utils::mipmap::MIPMapFilterOptions;
|
||||
use crate::utils::parameters::TextureParameterDictionary;
|
||||
use crate::utils::{FileLoc, TextureParameterDictionary};
|
||||
use shared::textures::*;
|
||||
use shared::utils::Transform;
|
||||
use shared::images::
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
#[enum_dispatch(FloatTextureTrait)]
|
||||
|
|
@ -23,6 +23,33 @@ pub enum FloatTexture {
|
|||
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 {
|
||||
pub fn create(
|
||||
name: &str,
|
||||
|
|
@ -223,3 +250,41 @@ struct TexInfo {
|
|||
wrap_mode: WrapMode,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct FBmTexture;
|
||||
impl FloatTextureTrait for FBmTexture {}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::texture::ImageTextureBase;
|
||||
use crate::core::texture::ImageTextureBase;
|
||||
use crate::utils::{FileLoc, TextureParameterDictionary};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumImageTexture {
|
||||
base: ImageTextureBase,
|
||||
spectrum_type: SpectrumType,
|
||||
pub base: ImageTextureBase,
|
||||
pub spectrum_type: SpectrumType,
|
||||
}
|
||||
|
||||
impl SpectrumImageTexture {
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct MarbleTexture;
|
||||
impl SpectrumTextureTrait for MarbleTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]
|
||||
pub struct FloatMixTexture {
|
||||
tex1: Arc<FloatTexture>,
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
pub struct FloatPtexTexture;
|
||||
impl FloatTextureTrait for FloatPtexTexture {}
|
||||
pub struct PtexTextureBase {
|
||||
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)]
|
||||
pub struct SpectrumPtexTexture;
|
||||
pub struct SpectrumPtexTexture {
|
||||
pub base: PtexTextureBase,
|
||||
pub spectrum_type: SpectrumType,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumPtexTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
pub struct FloatScaledTexture {
|
||||
tex: Arc<FloatTexture>,
|
||||
|
|
@ -18,14 +26,14 @@ impl FloatScaledTexture {
|
|||
let mut scale = params.get_float_texture("scale", 1.);
|
||||
|
||||
for _ in 0..2 {
|
||||
if let FloatTexture::FloatConstant(c_tex) = &*scale {
|
||||
if let FloatTexture::Constant(c_tex) = &*scale {
|
||||
let cs = c_tex.value;
|
||||
if cs == 1.0 {
|
||||
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();
|
||||
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)]
|
||||
pub struct SpectrumScaledTexture {
|
||||
tex: Box<SpectrumTexture>,
|
||||
scale: Box<FloatTexture>,
|
||||
tex: Arc<SpectrumTexture>,
|
||||
scale: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumScaledTexture {
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct WindyTexture;
|
||||
impl FloatTextureTrait for WindyTexture {}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct WrinkledTexture;
|
||||
impl FloatTextureTrait for WrinkledTexture {}
|
||||
|
|
@ -5,5 +5,9 @@ pub mod mipmap;
|
|||
pub mod parameters;
|
||||
pub mod parser;
|
||||
|
||||
pub use error::FileLoc;
|
||||
pub use file::{read_float_file, resolve_filename};
|
||||
pub use parameters::{
|
||||
ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary,
|
||||
};
|
||||
pub use strings::*;
|
||||
|
|
|
|||
Loading…
Reference in a new issue