Migrating to a more GPU friendly architecture, refactored texture handling, working on images and scene creation. God have mercy on my soul
This commit is contained in:
parent
8bcc2fb0c8
commit
2e9d3c7301
61 changed files with 1734 additions and 1716 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,3 +3,5 @@
|
|||
*.log
|
||||
*.bak
|
||||
flip.rs
|
||||
.vscode
|
||||
rust-analyzer.json
|
||||
|
|
|
|||
13
Cargo.toml
13
Cargo.toml
|
|
@ -11,10 +11,6 @@ use_nvtx = []
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
bitflags = "2.10.0"
|
||||
bumpalo = "3.19.0"
|
||||
bytemuck = { version = "1.24.0", features = ["derive"] }
|
||||
enum_dispatch = "0.3.13"
|
||||
exr = "1.73.0"
|
||||
flate2 = "1.1.5"
|
||||
gpu = "0.2.3"
|
||||
|
|
@ -24,22 +20,19 @@ indicatif = "0.18.3"
|
|||
lazy_static = "1.5.0"
|
||||
log = "0.4.29"
|
||||
memmap2 = "0.9.9"
|
||||
num = "0.4.3"
|
||||
num-integer = "0.1.46"
|
||||
num-traits = "0.2.19"
|
||||
nvtx = "1.3.0"
|
||||
once_cell = "1.21.3"
|
||||
parking_lot = "0.12.5"
|
||||
paste = "1.0.15"
|
||||
qoi = "0.4.1"
|
||||
rand = "0.9.2"
|
||||
rayon = "1.11.0"
|
||||
smallvec = "1.15.1"
|
||||
thiserror = "2.0.17"
|
||||
unicode-normalization = "0.1.25"
|
||||
wgpu = "27.0.1"
|
||||
|
||||
shared = { path = "shared" }
|
||||
kernel = { path = "kernels" }
|
||||
kernels = { path = "kernels" }
|
||||
|
||||
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
|
||||
cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
|
||||
shared = { path = "../shared" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
|
|
|||
|
|
@ -4,3 +4,16 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.10.0"
|
||||
bumpalo = "3.19.1"
|
||||
bytemuck = { version = "1.24.0", features = ["derive"] }
|
||||
enum_dispatch = "0.3.13"
|
||||
log = "0.4.29"
|
||||
num = "0.4.3"
|
||||
num-integer = "0.1.46"
|
||||
num-traits = "0.2.19"
|
||||
once_cell = "1.21.3"
|
||||
smallvec = "1.15.1"
|
||||
|
||||
[features]
|
||||
cuda = []
|
||||
|
|
|
|||
9
shared/src/cameras/mod.rs
Normal file
9
shared/src/cameras/mod.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
mod orthographic;
|
||||
mod perspective;
|
||||
mod realistic;
|
||||
mod spherical;
|
||||
|
||||
pub use orthographic::OrthographicCamera;
|
||||
pub use perspective::PerspectiveCamera;
|
||||
pub use realistic::RealisticCamera;
|
||||
pub use spherical::SphericalCamera;
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::core::camera::{
|
||||
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform,
|
||||
};
|
||||
use crate::core::film::{Film, FilmTrait};
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
use bitflags::bitflags;
|
||||
use rand::distr::uniform::SampleUniform;
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::ops::Not;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,3 @@
|
|||
mod orthographic;
|
||||
mod perspective;
|
||||
mod realistic;
|
||||
mod spherical;
|
||||
|
||||
pub use orthographic::OrthographicCamera;
|
||||
pub use perspective::PerspectiveCamera;
|
||||
pub use realistic::RealisticCamera;
|
||||
pub use spherical::SphericalCamera;
|
||||
|
||||
use crate::core::film::{Film, FilmTrait};
|
||||
use crate::core::geometry::{
|
||||
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
|
||||
|
|
@ -17,7 +7,7 @@ use crate::core::medium::Medium;
|
|||
use crate::core::options::RenderingCoordinateSystem;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::CameraSample;
|
||||
use crate::image::ImageMetadata;
|
||||
use crate::images::ImageMetadata;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::math::lerp;
|
||||
|
|
@ -147,7 +137,7 @@ impl CameraBaseParameters {
|
|||
shutter_open,
|
||||
shutter_close,
|
||||
film,
|
||||
medium: Some(medium),
|
||||
medium_id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,14 +16,14 @@ use crate::spectra::{
|
|||
};
|
||||
use crate::utils::AtomicFloat;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::math::linear_least_squares;
|
||||
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
|
||||
use crate::utils::parameters::ParameterDictionary;
|
||||
use crate::utils::sampling::VarianceEstimator;
|
||||
use crate::utils::transform::AnimatedTransform;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use rayon::prelude::*;
|
||||
use pbrt::utils::error::FileLoc;
|
||||
// use rayon::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, OnceLock, atomic::AtomicUsize, atomic::Ordering};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ use crate::utils::parameters::ParameterDictionary;
|
|||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
use rand::Rng;
|
||||
use std::hash::Hash;
|
||||
|
||||
pub struct FilterSample {
|
||||
pub p: Point2f,
|
||||
pub weight: Float,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::utils::interval::Interval;
|
||||
use crate::utils::math::{next_float_down, next_float_up};
|
||||
use num_integer::Roots;
|
||||
use num_traits::{Float as NumFloat, FloatConst, Num, One, Signed, Zero};
|
||||
use std::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
|
||||
|
||||
|
|
|
|||
889
shared/src/core/light.rs
Normal file
889
shared/src/core/light.rs
Normal file
|
|
@ -0,0 +1,889 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
||||
Vector3f, VectorLike, cos_theta,
|
||||
};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::medium::MediumInterface;
|
||||
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,
|
||||
};
|
||||
use crate::utils::containers::InternCache;
|
||||
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
use bitflags::bitflags;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use crate::lights::diffuse::DiffuseAreaLight;
|
||||
use crate::lights::infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
||||
|
||||
use crate::lights::sampler::{LightSampler, LightSamplerTrait};
|
||||
|
||||
static SPECTRUM_CACHE: OnceLock<InternCache<DenselySampledSpectrum>> = OnceLock::new();
|
||||
|
||||
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
|
||||
SPECTRUM_CACHE.get_or_init(InternCache::new)
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LightType: u32 {
|
||||
const DeltaPosition = 1;
|
||||
const DeltaDirection = 2;
|
||||
const Area = 4;
|
||||
const Infinite = 8;
|
||||
}
|
||||
}
|
||||
|
||||
impl LightType {
|
||||
pub fn is_infinite(&self) -> bool {
|
||||
self.contains(LightType::Infinite)
|
||||
}
|
||||
|
||||
pub fn is_delta_light(&self) -> bool {
|
||||
self.contains(LightType::DeltaPosition) || self.contains(LightType::DeltaDirection)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightLeSample {
|
||||
l: SampledSpectrum,
|
||||
ray: Ray,
|
||||
intr: Option<Interaction>,
|
||||
pdf_pos: Float,
|
||||
pdf_dir: Float,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Default, Clone)]
|
||||
pub struct LightSampleContext {
|
||||
pub pi: Point3fi,
|
||||
pub n: Normal3f,
|
||||
pub ns: Normal3f,
|
||||
}
|
||||
|
||||
impl LightSampleContext {
|
||||
pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f) -> Self {
|
||||
Self { pi, n, ns }
|
||||
}
|
||||
|
||||
pub fn p(&self) -> Point3f {
|
||||
self.pi.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SurfaceInteraction> for LightSampleContext {
|
||||
fn from(si: &SurfaceInteraction) -> Self {
|
||||
Self {
|
||||
pi: si.common.pi,
|
||||
n: si.common.n,
|
||||
ns: si.shading.n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MediumInteraction> for LightSampleContext {
|
||||
fn from(mi: &MediumInteraction) -> Self {
|
||||
Self {
|
||||
pi: mi.common.pi,
|
||||
n: Normal3f::default(),
|
||||
ns: Normal3f::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Interaction> for LightSampleContext {
|
||||
fn from(intr: &Interaction) -> Self {
|
||||
match intr {
|
||||
Interaction::Surface(si) => Self {
|
||||
pi: si.common.pi,
|
||||
n: si.common.n,
|
||||
ns: si.shading.n,
|
||||
},
|
||||
|
||||
Interaction::Medium(mi) => Self {
|
||||
pi: mi.common.pi,
|
||||
n: mi.common.n,
|
||||
ns: mi.common.n,
|
||||
},
|
||||
|
||||
Interaction::Simple(sim) => Self {
|
||||
pi: sim.common.pi,
|
||||
n: sim.common.n,
|
||||
ns: sim.common.n,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightLiSample {
|
||||
pub l: SampledSpectrum,
|
||||
pub wi: Vector3f,
|
||||
pub pdf: Float,
|
||||
pub p_light: Arc<Interaction>,
|
||||
}
|
||||
|
||||
impl LightLiSample {
|
||||
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
|
||||
Self {
|
||||
l,
|
||||
wi,
|
||||
pdf,
|
||||
p_light: Arc::new(p_light),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LightBaseData {
|
||||
pub render_from_light: Transform,
|
||||
pub light_type: u32,
|
||||
pub mi_inside: i32,
|
||||
pub mi_outside: i32,
|
||||
}
|
||||
|
||||
impl LightBase {
|
||||
pub fn new(
|
||||
light_type: LightType,
|
||||
render_from_light: &TransformGeneric<Float>,
|
||||
medium_interface: &MediumInterface,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type,
|
||||
render_from_light: *render_from_light,
|
||||
medium_interface: medium_interface.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
SampledSpectrum::default()
|
||||
}
|
||||
|
||||
pub fn light_type(&self) -> LightType {
|
||||
self.light_type
|
||||
}
|
||||
|
||||
pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
SampledSpectrum::default()
|
||||
}
|
||||
|
||||
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
|
||||
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
|
||||
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
|
||||
cache.lookup(dense_spectrum)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightBounds {
|
||||
bounds: Bounds3f,
|
||||
phi: Float,
|
||||
w: Vector3f,
|
||||
cos_theta_o: Float,
|
||||
cos_theta_e: Float,
|
||||
two_sided: bool,
|
||||
}
|
||||
|
||||
impl LightBounds {
|
||||
pub fn new(
|
||||
bounds: &Bounds3f,
|
||||
w: Vector3f,
|
||||
phi: Float,
|
||||
cos_theta_o: Float,
|
||||
cos_theta_e: Float,
|
||||
two_sided: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
bounds: *bounds,
|
||||
phi,
|
||||
w,
|
||||
cos_theta_o,
|
||||
cos_theta_e,
|
||||
two_sided,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn centroid(&self) -> Point3f {
|
||||
self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
|
||||
}
|
||||
|
||||
pub fn importance(&self, p: Point3f, n: Normal3f) -> Float {
|
||||
// Compute clamped squared distance to reference point
|
||||
let pc = self.centroid();
|
||||
let d2_raw = p.distance_squared(pc);
|
||||
let d2 = d2_raw.max(self.bounds.diagonal().norm()) / 2.;
|
||||
let cos_sub_clamped = |sin_theta_a: Float,
|
||||
cos_theta_a: Float,
|
||||
sin_theta_b: Float,
|
||||
cos_theta_b: Float|
|
||||
-> Float {
|
||||
if cos_theta_a > cos_theta_b {
|
||||
return 1.;
|
||||
}
|
||||
cos_theta_a * cos_theta_b + sin_theta_a * sin_theta_b
|
||||
};
|
||||
|
||||
let sin_sub_clamped = |sin_theta_a: Float,
|
||||
cos_theta_a: Float,
|
||||
sin_theta_b: Float,
|
||||
cos_theta_b: Float|
|
||||
-> Float {
|
||||
if cos_theta_a > cos_theta_b {
|
||||
return 1.;
|
||||
}
|
||||
sin_theta_a * cos_theta_b - cos_theta_a * sin_theta_b
|
||||
};
|
||||
|
||||
let wi = (p - pc).normalize();
|
||||
let mut cos_theta_w = self.w.dot(wi);
|
||||
if self.two_sided {
|
||||
cos_theta_w = cos_theta_w.abs();
|
||||
}
|
||||
let sin_theta_w = safe_sqrt(1. - square(cos_theta_w));
|
||||
let cos_theta_b = DirectionCone::bound_subtended_directions(&self.bounds, p).cos_theta;
|
||||
let sin_theta_b = safe_sqrt(1. - square(cos_theta_b));
|
||||
let sin_theta_o = safe_sqrt(1. - square(self.cos_theta_o));
|
||||
let cos_theta_x = cos_sub_clamped(sin_theta_w, cos_theta_w, sin_theta_o, self.cos_theta_o);
|
||||
let sin_theta_x = sin_sub_clamped(sin_theta_w, cos_theta_w, sin_theta_o, self.cos_theta_o);
|
||||
let cos_theta_p = cos_sub_clamped(sin_theta_x, cos_theta_x, sin_theta_b, cos_theta_b);
|
||||
|
||||
if cos_theta_p <= self.cos_theta_e {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
let mut importance = self.phi * cos_theta_p / d2;
|
||||
if n != Normal3f::new(0., 0., 0.) {
|
||||
let cos_theta_i = wi.abs_dot(n.into());
|
||||
let sin_theta_i = safe_sqrt(1. - square(cos_theta_i));
|
||||
let cos_thetap_i = cos_sub_clamped(sin_theta_i, cos_theta_i, sin_theta_b, cos_theta_b);
|
||||
importance *= cos_thetap_i;
|
||||
}
|
||||
importance
|
||||
}
|
||||
|
||||
pub fn union(a: &Self, b: &Self) -> Self {
|
||||
if a.phi == 0. {
|
||||
return a.clone();
|
||||
}
|
||||
if b.phi == 0. {
|
||||
return b.clone();
|
||||
}
|
||||
|
||||
let a_cone = DirectionCone::new(a.w, a.cos_theta_o);
|
||||
let b_cone = DirectionCone::new(b.w, b.cos_theta_o);
|
||||
let cone = DirectionCone::union(&a_cone, &b_cone);
|
||||
let cos_theta_o = cone.cos_theta;
|
||||
let cos_theta_e = a.cos_theta_e.min(b.cos_theta_e);
|
||||
LightBounds::new(
|
||||
&a.bounds.union(b.bounds),
|
||||
cone.w,
|
||||
a.phi + b.phi,
|
||||
cos_theta_o,
|
||||
cos_theta_e,
|
||||
a.two_sided || b.two_sided,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
||||
fn base(&self) -> &LightBase;
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample>;
|
||||
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float;
|
||||
fn l(
|
||||
&self,
|
||||
p: Point3f,
|
||||
n: Normal3f,
|
||||
uv: Point2f,
|
||||
w: Vector3f,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum;
|
||||
fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum;
|
||||
fn preprocess(&mut self, scene_bounds: &Bounds3f);
|
||||
fn bounds(&self) -> Option<LightBounds>;
|
||||
fn light_type(&self) -> LightType {
|
||||
self.base().light_type()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[enum_dispatch(LightTrait)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Light {
|
||||
DiffuseArea(DiffuseAreaLight),
|
||||
Distant(DistantLight),
|
||||
Goniometric(GoniometricLight),
|
||||
InfiniteUniform(InfiniteUniformLight),
|
||||
InfiniteImage(InfiniteImageLight),
|
||||
InfinitePortal(InfinitePortalLight),
|
||||
Point(PointLight),
|
||||
Projection(ProjectionLight),
|
||||
Spot(SpotLight),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DistantLightData {
|
||||
pub base: LightBaseData,
|
||||
pub lemit_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
}
|
||||
|
||||
impl DistantLightData {
|
||||
pub fn sample_li(
|
||||
&self,
|
||||
ctx_p: Point3f,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> (SampledSpectrum, Vector3f, Float, Point3f) {
|
||||
let wi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let p_outside = ctx_p + wi * 2. * self.scene_radius;
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
||||
let li = self.scale * spectrum.sample(lambda);
|
||||
(li, wi, 1.0, p_outside)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for DistantLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
self.scale * self.lemit.sample(&lambda) * PI * self.scene_radius.sqrt()
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let wi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let p_outside = ctx.p() + wi * 2. * self.scene_radius;
|
||||
|
||||
let li = self.scale * self.lemit.sample(lambda);
|
||||
let intr = SimpleInteraction::new(
|
||||
Point3fi::new_from_point(p_outside),
|
||||
0.0,
|
||||
Some(self.base.medium_interface.clone()),
|
||||
);
|
||||
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn preprocess(&mut self, scene_bounds: &Bounds3f) {
|
||||
let (center, radius) = scene_bounds.bounding_sphere();
|
||||
self.scene_center = center;
|
||||
self.scene_radius = radius;
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoniometricLight {
|
||||
pub base: LightBase,
|
||||
iemit: Arc<DenselySampledSpectrum>,
|
||||
scale: Float,
|
||||
image: Image,
|
||||
distrib: PiecewiseConstant2D,
|
||||
}
|
||||
|
||||
impl GoniometricLight {
|
||||
pub fn new(
|
||||
render_from_light: &TransformGeneric<Float>,
|
||||
medium_interface: &MediumInterface,
|
||||
iemit: Spectrum,
|
||||
scale: Float,
|
||||
image: Image,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
render_from_light,
|
||||
medium_interface,
|
||||
);
|
||||
|
||||
let i_interned = LightBase::lookup_spectrum(&iemit);
|
||||
let d = image.get_sampling_distribution_uniform();
|
||||
let distrib = PiecewiseConstant2D::new_with_data(&d);
|
||||
Self {
|
||||
base,
|
||||
iemit: i_interned,
|
||||
scale,
|
||||
image,
|
||||
distrib,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let uv = equal_area_sphere_to_square(w);
|
||||
self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for GoniometricLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum_y = 0.;
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
sum_y += self.image.get_channel(Point2i::new(x, y), 0);
|
||||
}
|
||||
}
|
||||
self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y
|
||||
/ (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
_lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PointLightData {
|
||||
pub base: LightBaseData,
|
||||
pub i_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
}
|
||||
|
||||
impl PointLightData {
|
||||
pub fn sample_li(
|
||||
&self,
|
||||
ctx_p: Point3f,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> (SampledSpectrum, Vector3f, Float, Point3fi) {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx_p).normalize();
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.i_coeffs);
|
||||
let li = self.scale * spectrum.sample(lambda) / p.distance_squared(ctx_p);
|
||||
(li, wi, 1.0, pi)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for PointLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p());
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
4. * PI * self.scale * self.i.sample(&lambda)
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
let p = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_point(Point3f::new(0., 0., 0.));
|
||||
let phi = 4. * PI * self.scale * self.i.max_value();
|
||||
Some(LightBounds::new(
|
||||
&Bounds3f::from_points(p, p),
|
||||
Vector3f::new(0., 0., 1.),
|
||||
phi,
|
||||
PI.cos(),
|
||||
(PI / 2.).cos(),
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ProjectionLightPOD {
|
||||
pub base: LightBasePOD,
|
||||
pub scale: Float,
|
||||
pub hither: Float,
|
||||
pub screen_from_light: Transform,
|
||||
pub screen_bounds: Bounds2f,
|
||||
pub image_id: u32,
|
||||
}
|
||||
|
||||
impl ProjectionLight {
|
||||
pub fn new(
|
||||
render_from_light: TransformGeneric<Float>,
|
||||
medium_interface: MediumInterface,
|
||||
image: Image,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
scale: Float,
|
||||
fov: Float,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
&render_from_light,
|
||||
&medium_interface,
|
||||
);
|
||||
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
|
||||
let screen_bounds = if aspect > 1. {
|
||||
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
|
||||
} else {
|
||||
Bounds2f::from_points(
|
||||
Point2f::new(-1., 1. / aspect),
|
||||
Point2f::new(1., 1. / aspect),
|
||||
)
|
||||
};
|
||||
|
||||
let hither = 1e-3;
|
||||
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
|
||||
let light_from_screen = screen_from_light.inverse();
|
||||
let opposite = (radians(fov) / 2.).tan();
|
||||
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
||||
let a = 4. * square(opposite) * aspect_ratio;
|
||||
let dwda = |p: Point2f| {
|
||||
let w =
|
||||
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
|
||||
cos_theta(w.normalize()).powi(3)
|
||||
};
|
||||
|
||||
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
||||
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
|
||||
|
||||
Self {
|
||||
base,
|
||||
image,
|
||||
image_color_space,
|
||||
screen_bounds,
|
||||
screen_from_light,
|
||||
light_from_screen,
|
||||
scale,
|
||||
hither,
|
||||
a,
|
||||
distrib,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
if w.z() < self.hither {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
let ps = self.screen_from_light.apply_to_point(w.into());
|
||||
if !self.screen_bounds.contains(Point2f::new(ps.x(), ps.y())) {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.lookup_nearest_channel(uv, c);
|
||||
}
|
||||
let s = RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
|
||||
self.scale * s.sample(&lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for ProjectionLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum = SampledSpectrum::new(0.);
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
let ps = self.screen_bounds.lerp(Point2f::new(
|
||||
(x as Float + 0.5) / self.image.resolution.x() as Float,
|
||||
(y as Float + 0.5) / self.image.resolution.y() as Float,
|
||||
));
|
||||
let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
|
||||
ps.x(),
|
||||
ps.y(),
|
||||
0.,
|
||||
)));
|
||||
let w = w_raw.normalize();
|
||||
let dwda = cos_theta(w).powi(3);
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.get_channel(Point2i::new(x, y), c);
|
||||
}
|
||||
|
||||
let s = RGBIlluminantSpectrum::new(
|
||||
self.image_color_space.as_ref(),
|
||||
RGB::clamp_zero(rgb),
|
||||
);
|
||||
sum += s.sample(&lambda) * dwda;
|
||||
}
|
||||
}
|
||||
self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
_lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
todo!()
|
||||
}
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
todo!()
|
||||
}
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SpotLightData {
|
||||
pub base: LightBaseData,
|
||||
pub iemit_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
pub cos_falloff_start: Float,
|
||||
pub cos_falloff_end: Float,
|
||||
}
|
||||
|
||||
impl SpotLightData {
|
||||
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let cos_theta = w.z(); // assuming normalized in light space
|
||||
let falloff = crate::utils::math::smooth_step(
|
||||
cos_theta,
|
||||
self.cos_falloff_end,
|
||||
self.cos_falloff_start,
|
||||
);
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.iemit_coeffs);
|
||||
falloff * self.scale * spectrum.sample(lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for SpotLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
let w_light = self.base.render_from_light.apply_inverse_vector(-wi);
|
||||
let li = self.i(w_light, *lambda) / p.distance_squared(ctx.p());
|
||||
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
self.scale
|
||||
* self.iemit.sample(&lambda)
|
||||
* 2.
|
||||
* PI
|
||||
* ((1. - self.cos_fallof_start) + (self.cos_fallof_start - self.cos_fallof_end) / 2.)
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
let p = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_point(Point3f::default());
|
||||
let w = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let phi = self.scale * self.iemit.max_value() * 4. * PI;
|
||||
let cos_theta_e = (self.cos_fallof_end.acos() - self.cos_fallof_start.acos()).cos();
|
||||
Some(LightBounds::new(
|
||||
&Bounds3f::from_points(p, p),
|
||||
w,
|
||||
phi,
|
||||
self.cos_fallof_start,
|
||||
cos_theta_e,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -884,11 +884,10 @@ impl MaterialTrait for MixMaterial {
|
|||
}
|
||||
|
||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||
None
|
||||
// panic!(
|
||||
// "MixMaterial::get_displacement() shouldn't be called. \
|
||||
// Displacement is not supported on Mix materials directly."
|
||||
// );
|
||||
panic!(
|
||||
"MixMaterial::get_displacement() shouldn't be called. \
|
||||
Displacement is not supported on Mix materials directly."
|
||||
);
|
||||
}
|
||||
|
||||
fn has_surface_scattering(&self) -> bool {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
pub mod aggregates;
|
||||
pub mod bssrdf;
|
||||
pub mod bxdf;
|
||||
pub mod camera;
|
||||
pub mod film;
|
||||
pub mod filter;
|
||||
pub mod geometry;
|
||||
pub mod interaction;
|
||||
pub mod light;
|
||||
pub mod material;
|
||||
pub mod medium;
|
||||
pub mod options;
|
||||
|
|
@ -12,4 +14,5 @@ pub mod pbrt;
|
|||
pub mod primitive;
|
||||
pub mod sampler;
|
||||
pub mod scattering;
|
||||
pub mod spectrum;
|
||||
pub mod texture;
|
||||
|
|
|
|||
|
|
@ -141,30 +141,22 @@ pub fn gamma(n: i32) -> Float {
|
|||
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
|
||||
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
// A macro to encapsulate the logic
|
||||
#[macro_export]
|
||||
macro_rules! check_rare {
|
||||
($frequency_threshold:expr, $condition:expr) => {
|
||||
// We only run the check periodically to reduce overhead.
|
||||
// PBRT's implementation does something similar.
|
||||
const CHECK_INTERVAL: u64 = 4096;
|
||||
|
||||
// Atomically increment the total call counter.
|
||||
// Ordering::Relaxed is fine because we don't need to synchronize memory.
|
||||
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);
|
||||
|
||||
if $condition {
|
||||
// If the rare condition is met, increment that counter.
|
||||
RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
|
||||
}
|
||||
|
||||
// Only perform the expensive division and check periodically.
|
||||
if (total_calls + 1) % CHECK_INTERVAL == 0 {
|
||||
let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
|
||||
if met_count > 0 {
|
||||
let frequency = met_count as f64 / (total_calls + 1) as f64;
|
||||
if frequency > $frequency_threshold {
|
||||
// In a real scenario, you might log an error or panic.
|
||||
panic!(
|
||||
"Rare event occurred with frequency {} which is > threshold {}",
|
||||
frequency, $frequency_threshold
|
||||
|
|
|
|||
0
shared/src/core/spectrum.rs
Normal file
0
shared/src/core/spectrum.rs
Normal file
|
|
@ -4,23 +4,16 @@ use crate::core::geometry::{
|
|||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::pbrt::{INV_2_PI, INV_PI, PI};
|
||||
use crate::image::WrapMode;
|
||||
use crate::images::WrapMode;
|
||||
use crate::spectra::color::ColorEncoding;
|
||||
use crate::spectra::{
|
||||
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths, Spectrum, SpectrumProvider,
|
||||
};
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::math::square;
|
||||
use crate::utils::mipmap::MIPMap;
|
||||
use crate::utils::mipmap::{MIPMapFilterOptions, MIPMapSample};
|
||||
use crate::utils::parameters::TextureParameterDictionary;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::path::Path;
|
||||
|
||||
|
|
@ -46,46 +39,6 @@ pub enum TextureMapping2D {
|
|||
Planar(PlanarMapping),
|
||||
}
|
||||
|
||||
impl TextureMapping2D {
|
||||
pub fn create(
|
||||
params: &TextureParameterDictionary,
|
||||
render_from_texture: &Transform,
|
||||
loc: &FileLoc,
|
||||
) -> Self {
|
||||
let mtype = params.get_one_string("mapping", "uv");
|
||||
match mtype.as_str() {
|
||||
"uv" => {
|
||||
let su = params.get_one_float("uscale", 1.);
|
||||
let sv = params.get_one_float("vscale", 1.);
|
||||
let du = params.get_one_float("udelta", 0.);
|
||||
let dv = params.get_one_float("vdelta", 0.);
|
||||
let mapping = UVMapping::new(su, sv, du, dv);
|
||||
TextureMapping2D::UV(mapping)
|
||||
}
|
||||
"spherical" => {
|
||||
let mapping = SphericalMapping::new(&render_from_texture.inverse());
|
||||
TextureMapping2D::Spherical(mapping)
|
||||
}
|
||||
"cylindrical" => {
|
||||
let mapping = CylindricalMapping::new(&render_from_texture.inverse());
|
||||
TextureMapping2D::Cylindrical(mapping)
|
||||
}
|
||||
"planar" => {
|
||||
let vs = params.get_one_vector3f("v1", Vector3f::new(1., 0., 0.));
|
||||
let vt = params.get_one_vector3f("v2", Vector3f::new(0., 1., 0.));
|
||||
let ds = params.get_one_float("udelta", 0.);
|
||||
let dt = params.get_one_float("vdelta", 0.);
|
||||
let mapping = PlanarMapping::new(&render_from_texture.inverse(), vs, vt, ds, dt);
|
||||
TextureMapping2D::Planar(mapping)
|
||||
}
|
||||
_ => {
|
||||
log::error!("{}: 2D texture mapping unknown {}", loc, mtype);
|
||||
TextureMapping2D::UV(UVMapping::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UVMapping {
|
||||
su: Float,
|
||||
|
|
@ -377,342 +330,6 @@ pub trait FloatTextureTrait: Send + Sync + std::fmt::Debug {
|
|||
}
|
||||
}
|
||||
|
||||
#[enum_dispatch(FloatTextureTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FloatTexture {
|
||||
FloatConstant(FloatConstantTexture),
|
||||
GPUFloatImage(GPUFloatImageTexture),
|
||||
FloatImage(FloatImageTexture),
|
||||
FloatMix(FloatMixTexture),
|
||||
FloatDirectionMix(FloatDirectionMixTexture),
|
||||
FloatScaled(FloatScaledTexture),
|
||||
FloatBilerp(FloatBilerpTexture),
|
||||
FloatCheckerboard(FloatCheckerboardTexture),
|
||||
FloatDots(FloatDotsTexture),
|
||||
FBm(FBmTexture),
|
||||
FloatPtex(FloatPtexTexture),
|
||||
GPUFLoatPtex(GPUFloatPtexTexture),
|
||||
Windy(WindyTexture),
|
||||
Wrinkled(WrinkledTexture),
|
||||
}
|
||||
|
||||
impl FloatTexture {
|
||||
pub fn create(
|
||||
name: &str,
|
||||
render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
gpu: bool,
|
||||
) -> Result<Self, String> {
|
||||
match name {
|
||||
"constant" => {
|
||||
let tex = FloatConstantTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatConstant(tex))
|
||||
}
|
||||
"scale" => Ok(FloatScaledTexture::create(render_from_texture, params, loc)),
|
||||
"mix" => {
|
||||
let tex = FloatMixTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatMix(tex))
|
||||
}
|
||||
"directionmix" => {
|
||||
let tex = FloatDirectionMixTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatDirectionMix(tex))
|
||||
}
|
||||
"bilerp" => {
|
||||
let tex = FloatBilerpTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatBilerp(tex))
|
||||
}
|
||||
"imagemap" => {
|
||||
if gpu {
|
||||
let tex = GPUFloatImageTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::GPUFloatImage(tex))
|
||||
} else {
|
||||
let tex = FloatImageTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatImage(tex))
|
||||
}
|
||||
}
|
||||
"checkerboard" => {
|
||||
let tex = FloatCheckerboardTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatCheckerboard(tex))
|
||||
}
|
||||
"dots" => {
|
||||
let tex = FloatDotsTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatDots(tex))
|
||||
}
|
||||
"fbm" => {
|
||||
let tex = FBmTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FBm(tex))
|
||||
}
|
||||
"wrinkled" => {
|
||||
let tex = WrinkledTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Wrinkled(tex))
|
||||
}
|
||||
"windy" => {
|
||||
let tex = WindyTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Windy(tex))
|
||||
}
|
||||
"ptex" => {
|
||||
if gpu {
|
||||
let tex = GPUFloatPtexTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::GPUFLoatPtex(tex))
|
||||
} else {
|
||||
let tex = FloatPtexTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FloatPtex(tex))
|
||||
};
|
||||
}
|
||||
_ => Err(format!("Float texture type '{}' unknown at {}", name, loc)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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(Debug, Clone)]
|
||||
pub struct GPUFloatImageTexture {
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for GPUFloatImageTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatImageTexture {
|
||||
base: ImageTextureBase,
|
||||
}
|
||||
|
||||
impl FloatImageTexture {
|
||||
pub fn new(
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
Self {
|
||||
base: ImageTextureBase::new(
|
||||
mapping,
|
||||
filename,
|
||||
filter_options,
|
||||
wrap_mode,
|
||||
scale,
|
||||
invert,
|
||||
encoding,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatImageTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatMixTexture {
|
||||
tex1: Arc<FloatTexture>,
|
||||
tex2: Arc<FloatTexture>,
|
||||
amount: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
impl FloatMixTexture {
|
||||
pub fn new(
|
||||
tex1: Arc<FloatTexture>,
|
||||
tex2: Arc<FloatTexture>,
|
||||
amount: Arc<FloatTexture>,
|
||||
) -> Self {
|
||||
Self { tex1, tex2, amount }
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
_render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
) -> Self {
|
||||
let tex1 = params.get_float_texture("tex1", 0.);
|
||||
let tex2 = params.get_float_texture("tex2", 1.);
|
||||
let amount = params.get_float_texture("amount", 0.5);
|
||||
Self::new(tex1, tex2, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatMixTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
let amt = self.amount.evaluate(ctx);
|
||||
let mut t1 = 0.;
|
||||
let mut t2 = 0.;
|
||||
if amt != 1. {
|
||||
t1 = self.tex1.evaluate(ctx);
|
||||
}
|
||||
if amt != 0. {
|
||||
t2 = self.tex2.evaluate(ctx);
|
||||
}
|
||||
(1. - amt) * t1 + amt * t2
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatDirectionMixTexture {
|
||||
tex1: Arc<FloatTexture>,
|
||||
tex2: Arc<FloatTexture>,
|
||||
dir: Vector3f,
|
||||
}
|
||||
|
||||
impl FloatDirectionMixTexture {
|
||||
pub fn new(tex1: Arc<FloatTexture>, tex2: Arc<FloatTexture>, dir: Vector3f) -> Self {
|
||||
Self { tex1, tex2, dir }
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
) -> Self {
|
||||
let dir_raw = params.get_one_vector3f("dir", Vector3f::new(0., 1., 0.));
|
||||
let dir = render_from_texture.apply_to_vector(dir_raw).normalize();
|
||||
let tex1 = params.get_float_texture("tex1", 0.);
|
||||
let tex2 = params.get_float_texture("tex2", 1.);
|
||||
Self::new(tex1, tex2, dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatDirectionMixTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatScaledTexture {
|
||||
tex: Arc<FloatTexture>,
|
||||
scale: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
impl FloatScaledTexture {
|
||||
pub fn new(tex: Arc<FloatTexture>, scale: Arc<FloatTexture>) -> Self {
|
||||
Self { tex, scale }
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
_render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
) -> FloatTexture {
|
||||
let mut tex = params.get_float_texture("tex", 1.);
|
||||
let mut scale = params.get_float_texture("scale", 1.);
|
||||
|
||||
for _ in 0..2 {
|
||||
if let FloatTexture::FloatConstant(c_tex) = &*scale {
|
||||
let cs = c_tex.value;
|
||||
if cs == 1.0 {
|
||||
return (*tex).clone();
|
||||
} else if let FloatTexture::FloatImage(img_tex) = &*tex {
|
||||
let mut image_copy = img_tex.clone();
|
||||
image_copy.base.multiply_scale(cs);
|
||||
return FloatTexture::FloatImage(image_copy).into();
|
||||
}
|
||||
}
|
||||
|
||||
std::mem::swap(&mut tex, &mut scale);
|
||||
}
|
||||
std::mem::swap(&mut tex, &mut scale);
|
||||
FloatTexture::FloatScaled(FloatScaledTexture::new(tex, scale))
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatScaledTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
let sc = self.scale.evaluate(ctx);
|
||||
if sc == 0. {
|
||||
return 0.;
|
||||
}
|
||||
self.tex.evaluate(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[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(Debug, Clone)]
|
||||
pub struct FloatCheckerboardTexture;
|
||||
impl FloatTextureTrait for FloatCheckerboardTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatDotsTexture;
|
||||
impl FloatTextureTrait for FloatDotsTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FBmTexture;
|
||||
impl FloatTextureTrait for FBmTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatPtexTexture;
|
||||
impl FloatTextureTrait for FloatPtexTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GPUFloatPtexTexture;
|
||||
impl FloatTextureTrait for GPUFloatPtexTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WindyTexture;
|
||||
impl FloatTextureTrait for WindyTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WrinkledTexture;
|
||||
impl FloatTextureTrait for WrinkledTexture {}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait SpectrumTextureTrait: Send + Sync + std::fmt::Debug {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
|
|
@ -720,72 +337,20 @@ pub trait SpectrumTextureTrait: Send + Sync + std::fmt::Debug {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub enum GPUFloatTexture {
|
||||
Constant(Float),
|
||||
Ptex(GPUFloatPtexTexture),
|
||||
Image(GPUFloatImageTexture),
|
||||
Mix(GPUFloatMixTexture),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[enum_dispatch(SpectrumTextureTrait)]
|
||||
pub enum SpectrumTexture {
|
||||
RGBConstant(RGBConstantTexture),
|
||||
RGBReflectanceConstant(RGBReflectanceConstantTexture),
|
||||
SpectrumConstant(SpectrumConstantTexture),
|
||||
SpectrumBilerp(SpectrumBilerpTexture),
|
||||
SpectrumCheckerboard(SpectrumCheckerboardTexture),
|
||||
SpectrumImage(SpectrumImageTexture),
|
||||
GPUSpectrumImage(GPUSpectrumImageTexture),
|
||||
Marble(MarbleTexture),
|
||||
SpectrumMix(SpectrumMixTexture),
|
||||
SpectrumDirectionMix(SpectrumDirectionMixTexture),
|
||||
SpectrumDots(SpectrumDotsTexture),
|
||||
SpectrumPtex(SpectrumPtexTexture),
|
||||
GPUSpectrumPtex(GPUSpectrumPtexTexture),
|
||||
SpectrumScaled(SpectrumScaledTexture),
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumBilerpTexture;
|
||||
impl SpectrumTextureTrait for SpectrumBilerpTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumCheckerboardTexture;
|
||||
impl SpectrumTextureTrait for SpectrumCheckerboardTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
Image(GPUSpectrumImageTexture),
|
||||
Ptex(GPUSpectrumPtexTexture),
|
||||
Mix(GPUSpectrumMixTexture),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
|
@ -794,237 +359,6 @@ pub enum SpectrumType {
|
|||
Albedo,
|
||||
Unbounded,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumImageTexture {
|
||||
base: ImageTextureBase,
|
||||
spectrum_type: SpectrumType,
|
||||
}
|
||||
|
||||
impl SpectrumImageTexture {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
encoding: ColorEncoding,
|
||||
spectrum_type: SpectrumType,
|
||||
) -> Self {
|
||||
let base = ImageTextureBase::new(
|
||||
mapping,
|
||||
filename,
|
||||
filter_options,
|
||||
wrap_mode,
|
||||
scale,
|
||||
invert,
|
||||
encoding,
|
||||
);
|
||||
|
||||
Self {
|
||||
base,
|
||||
spectrum_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumImageTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let mut c = self.base.mapping.map(ctx);
|
||||
c.st[1] = 1. - c.st[1];
|
||||
let dst0 = Vector2f::new(c.dsdx, c.dtdx);
|
||||
let dst1 = Vector2f::new(c.dsdy, c.dtdy);
|
||||
let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1);
|
||||
let rgb = RGB::clamp_zero(rgb_unclamp);
|
||||
if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
|
||||
match self.spectrum_type {
|
||||
SpectrumType::Unbounded => {
|
||||
return RGBUnboundedSpectrum::new(&cs, rgb).sample(lambda);
|
||||
}
|
||||
SpectrumType::Albedo => {
|
||||
return RGBAlbedoSpectrum::new(&cs, rgb).sample(lambda);
|
||||
}
|
||||
_ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda),
|
||||
}
|
||||
}
|
||||
assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]);
|
||||
SampledSpectrum::new(rgb[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GPUSpectrumImageTexture;
|
||||
impl SpectrumTextureTrait for GPUSpectrumImageTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MarbleTexture;
|
||||
impl SpectrumTextureTrait for MarbleTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumMixTexture;
|
||||
impl SpectrumTextureTrait for SpectrumMixTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumDirectionMixTexture {
|
||||
tex1: Box<SpectrumTexture>,
|
||||
tex2: Box<SpectrumTexture>,
|
||||
dir: Vector3f,
|
||||
}
|
||||
impl SpectrumTextureTrait for SpectrumDirectionMixTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let amt = ctx.n.abs_dot(self.dir.into());
|
||||
let mut t1 = SampledSpectrum::default();
|
||||
let mut t2 = SampledSpectrum::default();
|
||||
if amt != 0. {
|
||||
t1 = self.tex1.evaluate(ctx, lambda);
|
||||
}
|
||||
if amt != 1. {
|
||||
t2 = self.tex2.evaluate(ctx, lambda);
|
||||
}
|
||||
amt * t1 + (1. - amt) * t2
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumDotsTexture;
|
||||
impl SpectrumTextureTrait for SpectrumDotsTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumPtexTexture;
|
||||
impl SpectrumTextureTrait for SpectrumPtexTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GPUSpectrumPtexTexture;
|
||||
impl SpectrumTextureTrait for GPUSpectrumPtexTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumScaledTexture {
|
||||
tex: Box<SpectrumTexture>,
|
||||
scale: Box<FloatTexture>,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumScaledTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let sc = self.scale.evaluate(ctx);
|
||||
if sc == 0. {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
self.tex.evaluate(ctx, lambda) * sc
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
struct TexInfo {
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
encoding: ColorEncoding,
|
||||
}
|
||||
|
||||
static TEXTURE_CACHE: OnceLock<Mutex<HashMap<TexInfo, Arc<MIPMap>>>> = OnceLock::new();
|
||||
|
||||
fn get_texture_cache() -> &'static Mutex<HashMap<TexInfo, Arc<MIPMap>>> {
|
||||
TEXTURE_CACHE.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageTextureBase {
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
mipmap: Arc<MIPMap>,
|
||||
}
|
||||
|
||||
impl ImageTextureBase {
|
||||
pub fn new(
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
let tex_info = TexInfo {
|
||||
filename: filename.clone(),
|
||||
filter_options,
|
||||
wrap_mode,
|
||||
encoding,
|
||||
};
|
||||
|
||||
let cache_mutex = get_texture_cache();
|
||||
|
||||
{
|
||||
let cache = cache_mutex.lock().unwrap();
|
||||
if let Some(mipmap) = cache.get(&tex_info) {
|
||||
return Self {
|
||||
mapping,
|
||||
filename,
|
||||
scale,
|
||||
invert,
|
||||
mipmap: mipmap.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let path = Path::new(&filename);
|
||||
let mipmap_raw = MIPMap::create_from_file(path, filter_options, wrap_mode, encoding)
|
||||
.expect("Failed to create MIPMap from file");
|
||||
|
||||
let mipmap_arc = Arc::new(mipmap_raw);
|
||||
|
||||
{
|
||||
let mut cache = cache_mutex.lock().unwrap();
|
||||
|
||||
let stored_mipmap = cache.entry(tex_info).or_insert(mipmap_arc);
|
||||
|
||||
Self {
|
||||
mapping,
|
||||
filename,
|
||||
scale,
|
||||
invert,
|
||||
mipmap: stored_mipmap.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_cache() {
|
||||
let mut cache = get_texture_cache().lock().unwrap();
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
pub fn multiply_scale(&mut self, s: Float) {
|
||||
self.scale *= s;
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TextureEvaluator: Send + Sync {
|
||||
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float;
|
||||
fn evaluate_spectrum(
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
#![feature(float_erf)]
|
||||
#![feature(f16)]
|
||||
|
||||
mod camera;
|
||||
mod cameras;
|
||||
mod core;
|
||||
mod data;
|
||||
mod image;
|
||||
mod images;
|
||||
mod integrators;
|
||||
mod lights;
|
||||
mod shapes;
|
||||
mod spectra;
|
||||
mod textures;
|
||||
mod utils;
|
||||
|
||||
pub use core::pbrt::*;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::geometry::*;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::spectra::*;
|
||||
use crate::shapes::GpuShapeHandle;
|
||||
use crate::textures::GpuFloatTextureHandle;
|
||||
// use crate::core::texture::GpuFloatTextureHandle;
|
||||
// use crate::shapes::GpuShapeHandle;
|
||||
use crate::spectra::*;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
|
|
|
|||
|
|
@ -18,17 +18,18 @@ use crate::image::{PixelFormat, WrapMode};
|
|||
use std::path::Path;
|
||||
|
||||
use super::{
|
||||
Arc, Bounds2f, Bounds3f, DenselySampledSpectrum, Float, Image, Interaction, LightBase,
|
||||
Arc, Bounds2f, Bounds3f, DenselySampledSpectrum, Float, Image, Interaction, LightBaseData,
|
||||
LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, MediumInterface,
|
||||
Normal3f, PI, Point2f, Point2i, Point3f, Ray, SampledSpectrum, SampledWavelengths,
|
||||
SimpleInteraction, Spectrum, TransformGeneric, Vector3f, VectorLike,
|
||||
equal_area_sphere_to_square, square,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InfiniteUniformLight {
|
||||
base: LightBase,
|
||||
lemit: Arc<DenselySampledSpectrum>,
|
||||
base: LightBaseData,
|
||||
lemit: u32,
|
||||
scale: Float,
|
||||
scene_center: Point3f,
|
||||
scene_radius: Float,
|
||||
|
|
@ -124,7 +125,7 @@ impl LightTrait for InfiniteUniformLight {
|
|||
pub struct InfiniteImageLight {
|
||||
base: LightBase,
|
||||
image: Image,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
image_color_space: u32,
|
||||
scale: Float,
|
||||
scene_radius: Float,
|
||||
scene_center: Point3f,
|
||||
|
|
@ -313,7 +314,7 @@ impl LightTrait for InfiniteImageLight {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InfinitePortalLight {
|
||||
pub base: LightBase,
|
||||
pub base: LightBaseData,
|
||||
pub image: Image,
|
||||
pub image_color_space: Arc<RGBColorSpace>,
|
||||
pub scale: Float,
|
||||
|
|
|
|||
|
|
@ -1,919 +1,3 @@
|
|||
pub mod diffuse;
|
||||
pub mod infinite;
|
||||
pub mod sampler;
|
||||
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
||||
Vector3f, VectorLike, cos_theta,
|
||||
};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::medium::MediumInterface;
|
||||
use crate::core::pbrt::{Float, PI};
|
||||
use crate::image::Image;
|
||||
use crate::spectra::{
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum,
|
||||
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumProvider,
|
||||
};
|
||||
use crate::utils::containers::InternCache;
|
||||
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
use bitflags::bitflags;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use diffuse::DiffuseAreaLight;
|
||||
use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
||||
|
||||
pub use sampler::{LightSampler, LightSamplerTrait};
|
||||
|
||||
static SPECTRUM_CACHE: OnceLock<InternCache<DenselySampledSpectrum>> = OnceLock::new();
|
||||
|
||||
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
|
||||
SPECTRUM_CACHE.get_or_init(InternCache::new)
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LightType: u32 {
|
||||
const DeltaPosition = 1;
|
||||
const DeltaDirection = 2;
|
||||
const Area = 4;
|
||||
const Infinite = 8;
|
||||
}
|
||||
}
|
||||
|
||||
impl LightType {
|
||||
pub fn is_infinite(&self) -> bool {
|
||||
self.contains(LightType::Infinite)
|
||||
}
|
||||
|
||||
pub fn is_delta_light(&self) -> bool {
|
||||
self.contains(LightType::DeltaPosition) || self.contains(LightType::DeltaDirection)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightLeSample {
|
||||
l: SampledSpectrum,
|
||||
ray: Ray,
|
||||
intr: Option<Interaction>,
|
||||
pdf_pos: Float,
|
||||
pdf_dir: Float,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Default, Clone)]
|
||||
pub struct LightSampleContext {
|
||||
pub pi: Point3fi,
|
||||
pub n: Normal3f,
|
||||
pub ns: Normal3f,
|
||||
}
|
||||
|
||||
impl LightSampleContext {
|
||||
pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f) -> Self {
|
||||
Self { pi, n, ns }
|
||||
}
|
||||
|
||||
pub fn p(&self) -> Point3f {
|
||||
self.pi.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SurfaceInteraction> for LightSampleContext {
|
||||
fn from(si: &SurfaceInteraction) -> Self {
|
||||
Self {
|
||||
pi: si.common.pi,
|
||||
n: si.common.n,
|
||||
ns: si.shading.n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MediumInteraction> for LightSampleContext {
|
||||
fn from(mi: &MediumInteraction) -> Self {
|
||||
Self {
|
||||
pi: mi.common.pi,
|
||||
n: Normal3f::default(),
|
||||
ns: Normal3f::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Interaction> for LightSampleContext {
|
||||
fn from(intr: &Interaction) -> Self {
|
||||
match intr {
|
||||
Interaction::Surface(si) => Self {
|
||||
pi: si.common.pi,
|
||||
n: si.common.n,
|
||||
ns: si.shading.n,
|
||||
},
|
||||
|
||||
Interaction::Medium(mi) => Self {
|
||||
pi: mi.common.pi,
|
||||
n: mi.common.n,
|
||||
ns: mi.common.n,
|
||||
},
|
||||
|
||||
Interaction::Simple(sim) => Self {
|
||||
pi: sim.common.pi,
|
||||
n: sim.common.n,
|
||||
ns: sim.common.n,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightLiSample {
|
||||
pub l: SampledSpectrum,
|
||||
pub wi: Vector3f,
|
||||
pub pdf: Float,
|
||||
pub p_light: Arc<Interaction>,
|
||||
}
|
||||
|
||||
impl LightLiSample {
|
||||
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
|
||||
Self {
|
||||
l,
|
||||
wi,
|
||||
pdf,
|
||||
p_light: Arc::new(p_light),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightBase {
|
||||
pub light_type: LightType,
|
||||
pub render_from_light: TransformGeneric<Float>,
|
||||
pub medium_interface: MediumInterface,
|
||||
}
|
||||
|
||||
impl LightBase {
|
||||
pub fn new(
|
||||
light_type: LightType,
|
||||
render_from_light: &TransformGeneric<Float>,
|
||||
medium_interface: &MediumInterface,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type,
|
||||
render_from_light: *render_from_light,
|
||||
medium_interface: medium_interface.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
SampledSpectrum::default()
|
||||
}
|
||||
|
||||
pub fn light_type(&self) -> LightType {
|
||||
self.light_type
|
||||
}
|
||||
|
||||
pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
SampledSpectrum::default()
|
||||
}
|
||||
|
||||
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
|
||||
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
|
||||
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
|
||||
cache.lookup(dense_spectrum)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightBounds {
|
||||
bounds: Bounds3f,
|
||||
phi: Float,
|
||||
w: Vector3f,
|
||||
cos_theta_o: Float,
|
||||
cos_theta_e: Float,
|
||||
two_sided: bool,
|
||||
}
|
||||
|
||||
impl LightBounds {
|
||||
pub fn new(
|
||||
bounds: &Bounds3f,
|
||||
w: Vector3f,
|
||||
phi: Float,
|
||||
cos_theta_o: Float,
|
||||
cos_theta_e: Float,
|
||||
two_sided: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
bounds: *bounds,
|
||||
phi,
|
||||
w,
|
||||
cos_theta_o,
|
||||
cos_theta_e,
|
||||
two_sided,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn centroid(&self) -> Point3f {
|
||||
self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
|
||||
}
|
||||
|
||||
pub fn importance(&self, p: Point3f, n: Normal3f) -> Float {
|
||||
// Compute clamped squared distance to reference point
|
||||
let pc = self.centroid();
|
||||
let d2_raw = p.distance_squared(pc);
|
||||
let d2 = d2_raw.max(self.bounds.diagonal().norm()) / 2.;
|
||||
let cos_sub_clamped = |sin_theta_a: Float,
|
||||
cos_theta_a: Float,
|
||||
sin_theta_b: Float,
|
||||
cos_theta_b: Float|
|
||||
-> Float {
|
||||
if cos_theta_a > cos_theta_b {
|
||||
return 1.;
|
||||
}
|
||||
cos_theta_a * cos_theta_b + sin_theta_a * sin_theta_b
|
||||
};
|
||||
|
||||
let sin_sub_clamped = |sin_theta_a: Float,
|
||||
cos_theta_a: Float,
|
||||
sin_theta_b: Float,
|
||||
cos_theta_b: Float|
|
||||
-> Float {
|
||||
if cos_theta_a > cos_theta_b {
|
||||
return 1.;
|
||||
}
|
||||
sin_theta_a * cos_theta_b - cos_theta_a * sin_theta_b
|
||||
};
|
||||
|
||||
let wi = (p - pc).normalize();
|
||||
let mut cos_theta_w = self.w.dot(wi);
|
||||
if self.two_sided {
|
||||
cos_theta_w = cos_theta_w.abs();
|
||||
}
|
||||
let sin_theta_w = safe_sqrt(1. - square(cos_theta_w));
|
||||
let cos_theta_b = DirectionCone::bound_subtended_directions(&self.bounds, p).cos_theta;
|
||||
let sin_theta_b = safe_sqrt(1. - square(cos_theta_b));
|
||||
let sin_theta_o = safe_sqrt(1. - square(self.cos_theta_o));
|
||||
let cos_theta_x = cos_sub_clamped(sin_theta_w, cos_theta_w, sin_theta_o, self.cos_theta_o);
|
||||
let sin_theta_x = sin_sub_clamped(sin_theta_w, cos_theta_w, sin_theta_o, self.cos_theta_o);
|
||||
let cos_theta_p = cos_sub_clamped(sin_theta_x, cos_theta_x, sin_theta_b, cos_theta_b);
|
||||
|
||||
if cos_theta_p <= self.cos_theta_e {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
let mut importance = self.phi * cos_theta_p / d2;
|
||||
if n != Normal3f::new(0., 0., 0.) {
|
||||
let cos_theta_i = wi.abs_dot(n.into());
|
||||
let sin_theta_i = safe_sqrt(1. - square(cos_theta_i));
|
||||
let cos_thetap_i = cos_sub_clamped(sin_theta_i, cos_theta_i, sin_theta_b, cos_theta_b);
|
||||
importance *= cos_thetap_i;
|
||||
}
|
||||
importance
|
||||
}
|
||||
|
||||
pub fn union(a: &Self, b: &Self) -> Self {
|
||||
if a.phi == 0. {
|
||||
return a.clone();
|
||||
}
|
||||
if b.phi == 0. {
|
||||
return b.clone();
|
||||
}
|
||||
|
||||
let a_cone = DirectionCone::new(a.w, a.cos_theta_o);
|
||||
let b_cone = DirectionCone::new(b.w, b.cos_theta_o);
|
||||
let cone = DirectionCone::union(&a_cone, &b_cone);
|
||||
let cos_theta_o = cone.cos_theta;
|
||||
let cos_theta_e = a.cos_theta_e.min(b.cos_theta_e);
|
||||
LightBounds::new(
|
||||
&a.bounds.union(b.bounds),
|
||||
cone.w,
|
||||
a.phi + b.phi,
|
||||
cos_theta_o,
|
||||
cos_theta_e,
|
||||
a.two_sided || b.two_sided,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
||||
fn base(&self) -> &LightBase;
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample>;
|
||||
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float;
|
||||
fn l(
|
||||
&self,
|
||||
p: Point3f,
|
||||
n: Normal3f,
|
||||
uv: Point2f,
|
||||
w: Vector3f,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum;
|
||||
fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum;
|
||||
fn preprocess(&mut self, scene_bounds: &Bounds3f);
|
||||
fn bounds(&self) -> Option<LightBounds>;
|
||||
fn light_type(&self) -> LightType {
|
||||
self.base().light_type()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[enum_dispatch(LightTrait)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Light {
|
||||
DiffuseArea(DiffuseAreaLight),
|
||||
Distant(DistantLight),
|
||||
Goniometric(GoniometricLight),
|
||||
InfiniteUniform(InfiniteUniformLight),
|
||||
InfiniteImage(InfiniteImageLight),
|
||||
InfinitePortal(InfinitePortalLight),
|
||||
Point(PointLight),
|
||||
Projection(ProjectionLight),
|
||||
Spot(SpotLight),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DistantLight {
|
||||
pub base: LightBase,
|
||||
lemit: Arc<DenselySampledSpectrum>,
|
||||
scale: Float,
|
||||
scene_center: Point3f,
|
||||
scene_radius: Float,
|
||||
}
|
||||
|
||||
impl DistantLight {
|
||||
pub fn new(render_from_light: &TransformGeneric<Float>, lemit: Spectrum, scale: Float) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaDirection,
|
||||
render_from_light,
|
||||
&MediumInterface::default(),
|
||||
);
|
||||
|
||||
let l_interned = LightBase::lookup_spectrum(&lemit);
|
||||
Self {
|
||||
base,
|
||||
lemit: l_interned,
|
||||
scale,
|
||||
scene_center: Point3f::default(),
|
||||
scene_radius: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for DistantLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
self.scale * self.lemit.sample(&lambda) * PI * self.scene_radius.sqrt()
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let wi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let p_outside = ctx.p() + wi * 2. * self.scene_radius;
|
||||
|
||||
let li = self.scale * self.lemit.sample(lambda);
|
||||
let intr = SimpleInteraction::new(
|
||||
Point3fi::new_from_point(p_outside),
|
||||
0.0,
|
||||
Some(self.base.medium_interface.clone()),
|
||||
);
|
||||
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn preprocess(&mut self, scene_bounds: &Bounds3f) {
|
||||
let (center, radius) = scene_bounds.bounding_sphere();
|
||||
self.scene_center = center;
|
||||
self.scene_radius = radius;
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoniometricLight {
|
||||
pub base: LightBase,
|
||||
iemit: Arc<DenselySampledSpectrum>,
|
||||
scale: Float,
|
||||
image: Image,
|
||||
distrib: PiecewiseConstant2D,
|
||||
}
|
||||
|
||||
impl GoniometricLight {
|
||||
pub fn new(
|
||||
render_from_light: &TransformGeneric<Float>,
|
||||
medium_interface: &MediumInterface,
|
||||
iemit: Spectrum,
|
||||
scale: Float,
|
||||
image: Image,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
render_from_light,
|
||||
medium_interface,
|
||||
);
|
||||
|
||||
let i_interned = LightBase::lookup_spectrum(&iemit);
|
||||
let d = image.get_sampling_distribution_uniform();
|
||||
let distrib = PiecewiseConstant2D::new_with_data(&d);
|
||||
Self {
|
||||
base,
|
||||
iemit: i_interned,
|
||||
scale,
|
||||
image,
|
||||
distrib,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let uv = equal_area_sphere_to_square(w);
|
||||
self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for GoniometricLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum_y = 0.;
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
sum_y += self.image.get_channel(Point2i::new(x, y), 0);
|
||||
}
|
||||
}
|
||||
self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y
|
||||
/ (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
_lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PointLight {
|
||||
base: LightBase,
|
||||
i: Arc<DenselySampledSpectrum>,
|
||||
scale: Float,
|
||||
}
|
||||
|
||||
impl PointLight {
|
||||
pub fn new(
|
||||
render_from_light: TransformGeneric<Float>,
|
||||
medium_interface: MediumInterface,
|
||||
i: &Spectrum,
|
||||
scale: Float,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
&render_from_light,
|
||||
&medium_interface,
|
||||
);
|
||||
|
||||
let i_interned = LightBase::lookup_spectrum(i);
|
||||
|
||||
Self {
|
||||
base,
|
||||
i: i_interned,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for PointLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p());
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
4. * PI * self.scale * self.i.sample(&lambda)
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
let p = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_point(Point3f::new(0., 0., 0.));
|
||||
let phi = 4. * PI * self.scale * self.i.max_value();
|
||||
Some(LightBounds::new(
|
||||
&Bounds3f::from_points(p, p),
|
||||
Vector3f::new(0., 0., 1.),
|
||||
phi,
|
||||
PI.cos(),
|
||||
(PI / 2.).cos(),
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProjectionLight {
|
||||
base: LightBase,
|
||||
image: Image,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
scale: Float,
|
||||
screen_bounds: Bounds2f,
|
||||
hither: Float,
|
||||
screen_from_light: TransformGeneric<Float>,
|
||||
light_from_screen: TransformGeneric<Float>,
|
||||
a: Float,
|
||||
distrib: PiecewiseConstant2D,
|
||||
}
|
||||
|
||||
impl ProjectionLight {
|
||||
pub fn new(
|
||||
render_from_light: TransformGeneric<Float>,
|
||||
medium_interface: MediumInterface,
|
||||
image: Image,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
scale: Float,
|
||||
fov: Float,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
&render_from_light,
|
||||
&medium_interface,
|
||||
);
|
||||
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
|
||||
let screen_bounds = if aspect > 1. {
|
||||
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
|
||||
} else {
|
||||
Bounds2f::from_points(
|
||||
Point2f::new(-1., 1. / aspect),
|
||||
Point2f::new(1., 1. / aspect),
|
||||
)
|
||||
};
|
||||
|
||||
let hither = 1e-3;
|
||||
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
|
||||
let light_from_screen = screen_from_light.inverse();
|
||||
let opposite = (radians(fov) / 2.).tan();
|
||||
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
||||
let a = 4. * square(opposite) * aspect_ratio;
|
||||
let dwda = |p: Point2f| {
|
||||
let w =
|
||||
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
|
||||
cos_theta(w.normalize()).powi(3)
|
||||
};
|
||||
|
||||
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
||||
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
|
||||
|
||||
Self {
|
||||
base,
|
||||
image,
|
||||
image_color_space,
|
||||
screen_bounds,
|
||||
screen_from_light,
|
||||
light_from_screen,
|
||||
scale,
|
||||
hither,
|
||||
a,
|
||||
distrib,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
if w.z() < self.hither {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
let ps = self.screen_from_light.apply_to_point(w.into());
|
||||
if !self.screen_bounds.contains(Point2f::new(ps.x(), ps.y())) {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.lookup_nearest_channel(uv, c);
|
||||
}
|
||||
let s = RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
|
||||
self.scale * s.sample(&lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for ProjectionLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum = SampledSpectrum::new(0.);
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
let ps = self.screen_bounds.lerp(Point2f::new(
|
||||
(x as Float + 0.5) / self.image.resolution.x() as Float,
|
||||
(y as Float + 0.5) / self.image.resolution.y() as Float,
|
||||
));
|
||||
let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
|
||||
ps.x(),
|
||||
ps.y(),
|
||||
0.,
|
||||
)));
|
||||
let w = w_raw.normalize();
|
||||
let dwda = cos_theta(w).powi(3);
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.get_channel(Point2i::new(x, y), c);
|
||||
}
|
||||
|
||||
let s = RGBIlluminantSpectrum::new(
|
||||
self.image_color_space.as_ref(),
|
||||
RGB::clamp_zero(rgb),
|
||||
);
|
||||
sum += s.sample(&lambda) * dwda;
|
||||
}
|
||||
}
|
||||
self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
_lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
todo!()
|
||||
}
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
todo!()
|
||||
}
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpotLight {
|
||||
base: LightBase,
|
||||
iemit: Arc<DenselySampledSpectrum>,
|
||||
scale: Float,
|
||||
cos_fallof_start: Float,
|
||||
cos_fallof_end: Float,
|
||||
}
|
||||
|
||||
impl SpotLight {
|
||||
pub fn new(
|
||||
render_from_light: &TransformGeneric<Float>,
|
||||
medium_interface: &MediumInterface,
|
||||
iemit: Spectrum,
|
||||
scale: Float,
|
||||
total_width: Float,
|
||||
fallof_start: Float,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
render_from_light,
|
||||
medium_interface,
|
||||
);
|
||||
|
||||
let i_interned = LightBase::lookup_spectrum(&iemit);
|
||||
let cos_fallof_end = radians(total_width).cos();
|
||||
let cos_fallof_start = radians(fallof_start).cos();
|
||||
assert!(fallof_start < total_width);
|
||||
Self {
|
||||
base,
|
||||
iemit: i_interned,
|
||||
scale,
|
||||
cos_fallof_start,
|
||||
cos_fallof_end,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
smooth_step(cos_theta(w), self.cos_fallof_end, self.cos_fallof_start)
|
||||
* self.scale
|
||||
* self.iemit.sample(&lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for SpotLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
let w_light = self.base.render_from_light.apply_inverse_vector(-wi);
|
||||
let li = self.i(w_light, *lambda) / p.distance_squared(ctx.p());
|
||||
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
self.scale
|
||||
* self.iemit.sample(&lambda)
|
||||
* 2.
|
||||
* PI
|
||||
* ((1. - self.cos_fallof_start) + (self.cos_fallof_start - self.cos_fallof_end) / 2.)
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
let p = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_point(Point3f::default());
|
||||
let w = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let phi = self.scale * self.iemit.max_value() * 4. * PI;
|
||||
let cos_theta_e = (self.cos_fallof_end.acos() - self.cos_fallof_start.acos()).cos();
|
||||
Some(LightBounds::new(
|
||||
&Bounds3f::from_points(p, p),
|
||||
w,
|
||||
phi,
|
||||
self.cos_fallof_start,
|
||||
cos_theta_e,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
36
shared/src/textures/image.rs
Normal file
36
shared/src/textures/image.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use crate::{
|
||||
core::texture::{
|
||||
FloatTextureTrait, ImageTextureBase, SpectrumTextureTrait, SpectrumType, TextureMapping2D,
|
||||
},
|
||||
spectra::RGBColorSpace,
|
||||
};
|
||||
|
||||
/* GPU heavy code, dont know if this will ever work the way Im doing things.
|
||||
* Leaving it here isolated, for careful handling */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GPUSpectrumImageTexture {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub tex_obj: u64,
|
||||
pub scale: Float,
|
||||
pub invert: bool,
|
||||
pub color_space: RGBColorSpace,
|
||||
pub spectrum_type: SpectrumType,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for GPUSpectrumImageTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GPUFloatImageTexture {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub tex_obj: u64,
|
||||
pub scale: Float,
|
||||
pub invert: bool,
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for GPUFloatImageTexture {}
|
||||
14
shared/src/textures/mix.rs
Normal file
14
shared/src/textures/mix.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct GPUFloatMixTexture {
|
||||
tex1: GPUFloatTexture,
|
||||
tex2: GPUFloatTexture,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct GPUFloatDirectionMixTexture {
|
||||
tex1: GPUFloatTexture,
|
||||
tex2: GPUFloatTexture,
|
||||
dir: Vector3f,
|
||||
}
|
||||
3
shared/src/textures/mod.rs
Normal file
3
shared/src/textures/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod image;
|
||||
pub mod mix;
|
||||
pub mod ptex;
|
||||
16
shared/src/textures/ptex.rs
Normal file
16
shared/src/textures/ptex.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait, TextureEvalContext};
|
||||
use crate::spectra::{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;
|
||||
impl FloatTextureTrait for GPUFloatPtexTexture {}
|
||||
|
|
@ -10,15 +10,15 @@ use crate::core::geometry::{
|
|||
Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i,
|
||||
};
|
||||
|
||||
pub trait Interpolatable:
|
||||
Copy + Default + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self>
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> Interpolatable for T where
|
||||
T: Copy + Default + Add<Output = T> + Sub<Output = T> + Mul<Float, Output = T>
|
||||
{
|
||||
}
|
||||
// pub trait Interpolatable:
|
||||
// Copy + Default + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self>
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// impl<T> Interpolatable for T where
|
||||
// T: Copy + Default + Add<Output = T> + Sub<Output = T> + Mul<Float, Output = T>
|
||||
// {
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Array2D<T> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::fmt;
|
|||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::image::PixelFormat;
|
||||
use crate::images::PixelFormat;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LlsError {
|
||||
|
|
|
|||
|
|
@ -2,24 +2,17 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
|||
|
||||
pub mod containers;
|
||||
pub mod error;
|
||||
pub mod file;
|
||||
pub mod hash;
|
||||
pub mod interval;
|
||||
pub mod math;
|
||||
pub mod mesh;
|
||||
pub mod mipmap;
|
||||
pub mod parameters;
|
||||
pub mod parser;
|
||||
pub mod quaternion;
|
||||
pub mod rng;
|
||||
pub mod sampling;
|
||||
pub mod sobol;
|
||||
pub mod splines;
|
||||
pub mod strings;
|
||||
pub mod transform;
|
||||
|
||||
pub use file::{read_float_file, resolve_filename};
|
||||
pub use strings::*;
|
||||
pub use transform::{AnimatedTransform, Transform, TransformGeneric};
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
2
src/core/mod.rs
Normal file
2
src/core/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod scene;
|
||||
pub mod texture;
|
||||
|
|
@ -1,23 +1,23 @@
|
|||
use crate::Float;
|
||||
use crate::camera::{Camera, CameraTransform};
|
||||
use crate::core::film::{Film, FilmTrait};
|
||||
use crate::core::filter::Filter;
|
||||
use crate::core::geometry::{Point3f, Vector3f};
|
||||
use crate::core::medium::Medium;
|
||||
use crate::core::options::RenderingCoordinateSystem;
|
||||
use crate::core::sampler::Sampler;
|
||||
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||
use crate::image::Image;
|
||||
use crate::lights::Light;
|
||||
use crate::spectra::RGBColorSpace;
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::math::SquareMatrix;
|
||||
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
|
||||
use crate::utils::parser::ParserTarget;
|
||||
use crate::utils::transform::look_at;
|
||||
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
|
||||
use crate::utils::{normalize_utf8, resolve_filename};
|
||||
use parking_lot::Mutex;
|
||||
use shared::Float;
|
||||
use shared::core::camera::{Camera, CameraTransform};
|
||||
use shared::core::film::{Film, FilmTrait};
|
||||
use shared::core::filter::Filter;
|
||||
use shared::core::geometry::{Point3f, Vector3f};
|
||||
use shared::core::lights::Light;
|
||||
use shared::core::medium::Medium;
|
||||
use shared::core::options::RenderingCoordinateSystem;
|
||||
use shared::core::sampler::Sampler;
|
||||
use shared::core::texture::{FloatTexture, SpectrumTexture};
|
||||
use shared::images::Image;
|
||||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::error::FileLoc;
|
||||
use shared::utils::math::SquareMatrix;
|
||||
use shared::utils::parser::ParserTarget;
|
||||
use shared::utils::transform::look_at;
|
||||
use shared::utils::transform::{AnimatedTransform, TransformGeneric};
|
||||
use std::char::MAX;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::{Index as IndexTrait, IndexMut as IndexMutTrait};
|
||||
225
src/core/texture.rs
Normal file
225
src/core/texture.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
use crate::textures::*;
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::mipmap::MIPMapFilterOptions;
|
||||
use crate::utils::parameters::TextureParameterDictionary;
|
||||
use shared::utils::Transform;
|
||||
use shared::images::
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
#[enum_dispatch(FloatTextureTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FloatTexture {
|
||||
Constant(FloatConstantTexture),
|
||||
Image(FloatImageTexture),
|
||||
Mix(FloatMixTexture),
|
||||
DirectionMix(FloatDirectionMixTexture),
|
||||
Scaled(FloatScaledTexture),
|
||||
Bilerp(FloatBilerpTexture),
|
||||
Checkerboard(FloatCheckerboardTexture),
|
||||
Dots(FloatDotsTexture),
|
||||
FBm(FBmTexture),
|
||||
Ptex(FloatPtexTexture),
|
||||
Windy(WindyTexture),
|
||||
Wrinkled(WrinkledTexture),
|
||||
}
|
||||
|
||||
impl FloatTexture {
|
||||
pub fn create(
|
||||
name: &str,
|
||||
render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self, String> {
|
||||
match name {
|
||||
"constant" => {
|
||||
let tex = FloatConstantTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Constant(tex))
|
||||
}
|
||||
"scale" => Ok(FloatScaledTexture::create(render_from_texture, params, loc)),
|
||||
"mix" => {
|
||||
let tex = FloatMixTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Mix(tex))
|
||||
}
|
||||
"directionmix" => {
|
||||
let tex = FloatDirectionMixTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::DirectionMix(tex))
|
||||
}
|
||||
"bilerp" => {
|
||||
let tex = FloatBilerpTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Bilerp(tex))
|
||||
}
|
||||
"imagemap" => {
|
||||
let tex = FloatImageTexture::create(render_from_texture, params, loc);
|
||||
Ok(loatTexture::Image(tex))
|
||||
}
|
||||
"checkerboard" => {
|
||||
let tex = FloatCheckerboardTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Checkerboard(tex))
|
||||
}
|
||||
"dots" => {
|
||||
let tex = FloatDotsTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Dots(tex))
|
||||
}
|
||||
"fbm" => {
|
||||
let tex = FBmTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::FBm(tex))
|
||||
}
|
||||
"wrinkled" => {
|
||||
let tex = WrinkledTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Wrinkled(tex))
|
||||
}
|
||||
"windy" => {
|
||||
let tex = WindyTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Windy(tex))
|
||||
}
|
||||
"ptex" => {
|
||||
let tex = FloatPtexTexture::create(render_from_texture, params, loc);
|
||||
Ok(FloatTexture::Ptex(tex))
|
||||
}
|
||||
_ => Err(format!("Float texture type '{}' unknown at {}", name, loc)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[enum_dispatch(SpectrumTextureTrait)]
|
||||
pub enum SpectrumTexture {
|
||||
RGBConstant(RGBConstantTexture),
|
||||
RGBReflectanceConstant(RGBReflectanceConstantTexture),
|
||||
Constant(SpectrumConstantTexture),
|
||||
Bilerp(SpectrumBilerpTexture),
|
||||
Checkerboard(SpectrumCheckerboardTexture),
|
||||
Image(SpectrumImageTexture),
|
||||
Marble(MarbleTexture),
|
||||
Mix(SpectrumMixTexture),
|
||||
DirectionMix(SpectrumDirectionMixTexture),
|
||||
Dots(SpectrumDotsTexture),
|
||||
Ptex(SpectrumPtexTexture),
|
||||
Scaled(SpectrumScaledTexture),
|
||||
}
|
||||
|
||||
impl TextureMapping2D {
|
||||
pub fn create(
|
||||
params: &TextureParameterDictionary,
|
||||
render_from_texture: &Transform,
|
||||
loc: &FileLoc,
|
||||
) -> Self {
|
||||
let mtype = params.get_one_string("mapping", "uv");
|
||||
match mtype.as_str() {
|
||||
"uv" => {
|
||||
let su = params.get_one_float("uscale", 1.);
|
||||
let sv = params.get_one_float("vscale", 1.);
|
||||
let du = params.get_one_float("udelta", 0.);
|
||||
let dv = params.get_one_float("vdelta", 0.);
|
||||
let mapping = UVMapping::new(su, sv, du, dv);
|
||||
TextureMapping2D::UV(mapping)
|
||||
}
|
||||
"spherical" => {
|
||||
let mapping = SphericalMapping::new(&render_from_texture.inverse());
|
||||
TextureMapping2D::Spherical(mapping)
|
||||
}
|
||||
"cylindrical" => {
|
||||
let mapping = CylindricalMapping::new(&render_from_texture.inverse());
|
||||
TextureMapping2D::Cylindrical(mapping)
|
||||
}
|
||||
"planar" => {
|
||||
let vs = params.get_one_vector3f("v1", Vector3f::new(1., 0., 0.));
|
||||
let vt = params.get_one_vector3f("v2", Vector3f::new(0., 1., 0.));
|
||||
let ds = params.get_one_float("udelta", 0.);
|
||||
let dt = params.get_one_float("vdelta", 0.);
|
||||
let mapping = PlanarMapping::new(&render_from_texture.inverse(), vs, vt, ds, dt);
|
||||
TextureMapping2D::Planar(mapping)
|
||||
}
|
||||
_ => {
|
||||
log::error!("{}: 2D texture mapping unknown {}", loc, mtype);
|
||||
TextureMapping2D::UV(UVMapping::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TEXTURE_CACHE: OnceLock<Mutex<HashMap<TexInfo, Arc<MIPMap>>>> = OnceLock::new();
|
||||
|
||||
fn get_texture_cache() -> &'static Mutex<HashMap<TexInfo, Arc<MIPMap>>> {
|
||||
TEXTURE_CACHE.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageTextureBase {
|
||||
pub mapping: TextureMapping2D,
|
||||
pub filename: String,
|
||||
pub scale: Float,
|
||||
pub invert: bool,
|
||||
pub mipmap: Arc<MIPMap>,
|
||||
}
|
||||
|
||||
impl ImageTextureBase {
|
||||
pub fn new(
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
let tex_info = TexInfo {
|
||||
filename: filename.clone(),
|
||||
filter_options,
|
||||
wrap_mode,
|
||||
encoding,
|
||||
};
|
||||
|
||||
let cache_mutex = get_texture_cache();
|
||||
|
||||
{
|
||||
let cache = cache_mutex.lock().unwrap();
|
||||
if let Some(mipmap) = cache.get(&tex_info) {
|
||||
return Self {
|
||||
mapping,
|
||||
filename,
|
||||
scale,
|
||||
invert,
|
||||
mipmap: mipmap.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let path = Path::new(&filename);
|
||||
let mipmap_raw = MIPMap::create_from_file(path, filter_options, wrap_mode, encoding)
|
||||
.expect("Failed to create MIPMap from file");
|
||||
|
||||
let mipmap_arc = Arc::new(mipmap_raw);
|
||||
|
||||
{
|
||||
let mut cache = cache_mutex.lock().unwrap();
|
||||
|
||||
let stored_mipmap = cache.entry(tex_info).or_insert(mipmap_arc);
|
||||
|
||||
Self {
|
||||
mapping,
|
||||
filename,
|
||||
scale,
|
||||
invert,
|
||||
mipmap: stored_mipmap.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_cache() {
|
||||
let mut cache = get_texture_cache().lock().unwrap();
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
pub fn multiply_scale(&mut self, s: Float) {
|
||||
self.scale *= s;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
struct TexInfo {
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
encoding: ColorEncoding,
|
||||
}
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
pub mod scene;
|
||||
pub mod core;
|
||||
pub mod lights;
|
||||
pub mod textures;
|
||||
pub mod utils;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
use super::{
|
||||
DenselySampledSpectrum, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait,
|
||||
LightType, RGB, RGBColorSpace, RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths,
|
||||
Spectrum, SpectrumTrait,
|
||||
};
|
||||
use crate::core::geometry::{
|
||||
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::medium::MediumInterface;
|
||||
use crate::core::pbrt::{Float, PI};
|
||||
use crate::core::texture::{
|
||||
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
|
||||
UniversalTextureEvaluator,
|
||||
};
|
||||
use crate::image::Image;
|
||||
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait};
|
||||
use crate::utils::hash::hash_float;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
use shared::core::geometry::{
|
||||
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
|
||||
};
|
||||
use shared::core::interaction::{
|
||||
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
||||
};
|
||||
use shared::core::medium::MediumInterface;
|
||||
use shared::images::Image;
|
||||
use shared::spectra::{
|
||||
DenselySampledSpectrum, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait,
|
||||
LightType, RGB, RGBColorSpace, RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths,
|
||||
Spectrum, SpectrumTrait,
|
||||
};
|
||||
use shared::{Float, PI};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -38,7 +37,7 @@ pub struct DiffuseAreaLight {
|
|||
impl DiffuseAreaLight {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
render_from_light: TransformGeneric<Float>,
|
||||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
le: Spectrum,
|
||||
scale: Float,
|
||||
|
|
|
|||
1
src/lights/mod.rs
Normal file
1
src/lights/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod diffuse;
|
||||
48
src/textures/bilerp.rs
Normal file
48
src/textures/bilerp.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
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!()
|
||||
}
|
||||
}
|
||||
14
src/textures/checkerboard.rs
Normal file
14
src/textures/checkerboard.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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!()
|
||||
}
|
||||
}
|
||||
60
src/textures/constant.rs
Normal file
60
src/textures/constant.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
11
src/textures/dots.rs
Normal file
11
src/textures/dots.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#[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 {}
|
||||
3
src/textures/fbm.rs
Normal file
3
src/textures/fbm.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct FBmTexture;
|
||||
impl FloatTextureTrait for FBmTexture {}
|
||||
91
src/textures/image.rs
Normal file
91
src/textures/image.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
use crate::texture::ImageTextureBase;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumImageTexture {
|
||||
base: ImageTextureBase,
|
||||
spectrum_type: SpectrumType,
|
||||
}
|
||||
|
||||
impl SpectrumImageTexture {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
encoding: ColorEncoding,
|
||||
spectrum_type: SpectrumType,
|
||||
) -> Self {
|
||||
let base = ImageTextureBase::new(
|
||||
mapping,
|
||||
filename,
|
||||
filter_options,
|
||||
wrap_mode,
|
||||
scale,
|
||||
invert,
|
||||
encoding,
|
||||
);
|
||||
|
||||
Self {
|
||||
base,
|
||||
spectrum_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumImageTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let mut c = self.base.mapping.map(ctx);
|
||||
c.st[1] = 1. - c.st[1];
|
||||
let dst0 = Vector2f::new(c.dsdx, c.dtdx);
|
||||
let dst1 = Vector2f::new(c.dsdy, c.dtdy);
|
||||
let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1);
|
||||
let rgb = RGB::clamp_zero(rgb_unclamp);
|
||||
if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
|
||||
match self.spectrum_type {
|
||||
SpectrumType::Unbounded => {
|
||||
return RGBUnboundedSpectrum::new(&cs, rgb).sample(lambda);
|
||||
}
|
||||
SpectrumType::Albedo => {
|
||||
return RGBAlbedoSpectrum::new(&cs, rgb).sample(lambda);
|
||||
}
|
||||
_ => return RGBIlluminantSpectrum::new(&cs, rgb).sample(lambda),
|
||||
}
|
||||
}
|
||||
assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]);
|
||||
SampledSpectrum::new(rgb[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatImageTexture {
|
||||
base: ImageTextureBase,
|
||||
}
|
||||
|
||||
impl FloatImageTexture {
|
||||
pub fn new(
|
||||
mapping: TextureMapping2D,
|
||||
filename: String,
|
||||
filter_options: MIPMapFilterOptions,
|
||||
wrap_mode: WrapMode,
|
||||
scale: Float,
|
||||
invert: bool,
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
Self {
|
||||
base: ImageTextureBase::new(
|
||||
mapping,
|
||||
filename,
|
||||
filter_options,
|
||||
wrap_mode,
|
||||
scale,
|
||||
invert,
|
||||
encoding,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatImageTexture {}
|
||||
7
src/textures/marble.rs
Normal file
7
src/textures/marble.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct MarbleTexture;
|
||||
impl SpectrumTextureTrait for MarbleTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
77
src/textures/mix.rs
Normal file
77
src/textures/mix.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct FloatMixTexture {
|
||||
tex1: Arc<FloatTexture>,
|
||||
tex2: Arc<FloatTexture>,
|
||||
amount: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
impl FloatMixTexture {
|
||||
pub fn new(
|
||||
tex1: Arc<FloatTexture>,
|
||||
tex2: Arc<FloatTexture>,
|
||||
amount: Arc<FloatTexture>,
|
||||
) -> Self {
|
||||
Self { tex1, tex2, amount }
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
_render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
) -> Self {
|
||||
let tex1 = params.get_float_texture("tex1", 0.);
|
||||
let tex2 = params.get_float_texture("tex2", 1.);
|
||||
let amount = params.get_float_texture("amount", 0.5);
|
||||
Self::new(tex1, tex2, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatMixTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
let amt = self.amount.evaluate(ctx);
|
||||
let mut t1 = 0.;
|
||||
let mut t2 = 0.;
|
||||
if amt != 1. {
|
||||
t1 = self.tex1.evaluate(ctx);
|
||||
}
|
||||
if amt != 0. {
|
||||
t2 = self.tex2.evaluate(ctx);
|
||||
}
|
||||
(1. - amt) * t1 + amt * t2
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FloatDirectionMixTexture {
|
||||
tex1: Arc<FloatTexture>,
|
||||
tex2: Arc<FloatTexture>,
|
||||
dir: Vector3f,
|
||||
}
|
||||
|
||||
impl FloatDirectionMixTexture {
|
||||
pub fn new(tex1: Arc<FloatTexture>, tex2: Arc<FloatTexture>, dir: Vector3f) -> Self {
|
||||
Self { tex1, tex2, dir }
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
) -> Self {
|
||||
let dir_raw = params.get_one_vector3f("dir", Vector3f::new(0., 1., 0.));
|
||||
let dir = render_from_texture.apply_to_vector(dir_raw).normalize();
|
||||
let tex1 = params.get_float_texture("tex1", 0.);
|
||||
let tex2 = params.get_float_texture("tex2", 1.);
|
||||
Self::new(tex1, tex2, dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatDirectionMixTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpectrumMixTexture;
|
||||
impl SpectrumTextureTrait for SpectrumMixTexture {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpectrumDirectionMixTexture;
|
||||
impl SpectrumTextureTrait for SpectrumDirectionMixTexture {}
|
||||
25
src/textures/mod.rs
Normal file
25
src/textures/mod.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
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 scale;
|
||||
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 scale::*;
|
||||
pub use windy::*;
|
||||
pub use wrinkled::*;
|
||||
11
src/textures/ptex.rs
Normal file
11
src/textures/ptex.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct FloatPtexTexture;
|
||||
impl FloatTextureTrait for FloatPtexTexture {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumPtexTexture;
|
||||
impl SpectrumTextureTrait for SpectrumPtexTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
63
src/textures/scale.rs
Normal file
63
src/textures/scale.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct FloatScaledTexture {
|
||||
tex: Arc<FloatTexture>,
|
||||
scale: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
impl FloatScaledTexture {
|
||||
pub fn new(tex: Arc<FloatTexture>, scale: Arc<FloatTexture>) -> Self {
|
||||
Self { tex, scale }
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
_render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
) -> FloatTexture {
|
||||
let mut tex = params.get_float_texture("tex", 1.);
|
||||
let mut scale = params.get_float_texture("scale", 1.);
|
||||
|
||||
for _ in 0..2 {
|
||||
if let FloatTexture::FloatConstant(c_tex) = &*scale {
|
||||
let cs = c_tex.value;
|
||||
if cs == 1.0 {
|
||||
return (*tex).clone();
|
||||
} else if let FloatTexture::FloatImage(img_tex) = &*tex {
|
||||
let mut image_copy = img_tex.clone();
|
||||
image_copy.base.multiply_scale(cs);
|
||||
return FloatTexture::FloatImage(image_copy).into();
|
||||
}
|
||||
}
|
||||
|
||||
std::mem::swap(&mut tex, &mut scale);
|
||||
}
|
||||
std::mem::swap(&mut tex, &mut scale);
|
||||
FloatTexture::FloatScaled(FloatScaledTexture::new(tex, scale))
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for FloatScaledTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
let sc = self.scale.evaluate(ctx);
|
||||
if sc == 0. {
|
||||
return 0.;
|
||||
}
|
||||
self.tex.evaluate(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SpectrumScaledTexture {
|
||||
tex: Box<SpectrumTexture>,
|
||||
scale: Box<FloatTexture>,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumScaledTexture {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let sc = self.scale.evaluate(ctx);
|
||||
if sc == 0. {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
self.tex.evaluate(ctx, lambda) * sc
|
||||
}
|
||||
}
|
||||
3
src/textures/windy.rs
Normal file
3
src/textures/windy.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct WindyTexture;
|
||||
impl FloatTextureTrait for WindyTexture {}
|
||||
3
src/textures/wrinkled.rs
Normal file
3
src/textures/wrinkled.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct WrinkledTexture;
|
||||
impl FloatTextureTrait for WrinkledTexture {}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use pbrt::Float;
|
||||
use shared::Float;
|
||||
use std::fs;
|
||||
use std::io::{self, BufReader, Error, ErrorKind, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use crate::spectra::color::{ColorEncoding, LINEAR, SRGB};
|
||||
use crate::utils::error::ImageError;
|
||||
use anyhow::{Context, Result, bail};
|
||||
use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file};
|
||||
use image_rs::ImageReader;
|
||||
use image_rs::{DynamicImage, ImageBuffer, Rgb, Rgba};
|
||||
use pbrt::Float;
|
||||
use pbrt::image::{
|
||||
use shared::Float;
|
||||
use shared::images::{
|
||||
Image, ImageAndMetadata, ImageMetadata, PixelData, PixelFormat, Point2i, WrapMode,
|
||||
};
|
||||
use shared::spectra::color::{ColorEncoding, LINEAR, SRGB};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
|
||||
use std::path::Path;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::core::geometry::{Lerp, Point2f, Point2i, Vector2f, VectorLike};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::image::{Image, ImageAndMetadata, PixelData, PixelFormat, WrapMode, WrapMode2D};
|
||||
use crate::spectra::{RGB, RGBColorSpace, color::ColorEncoding};
|
||||
use crate::utils::math::{lerp, safe_sqrt, square};
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{Lerp, Point2f, Point2i, Vector2f, VectorLike};
|
||||
use shared::images::{Image, ImageAndMetadata, PixelData, PixelFormat, WrapMode, WrapMode2D};
|
||||
use shared::spectra::{RGB, RGBColorSpace, color::ColorEncoding};
|
||||
use shared::utils::math::{lerp, safe_sqrt, square};
|
||||
use std::path::Path;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
|
|
|||
|
|
@ -4,3 +4,6 @@ pub mod io;
|
|||
pub mod mipmap;
|
||||
pub mod parameters;
|
||||
pub mod parser;
|
||||
|
||||
pub use file::{read_float_file, resolve_filename};
|
||||
pub use strings::*;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::Float;
|
||||
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f};
|
||||
use crate::core::options::get_options;
|
||||
use crate::core::texture::{
|
||||
use crate::utils::error::FileLoc;
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f};
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::texture::{
|
||||
FloatConstantTexture, FloatTexture, SpectrumConstantTexture, SpectrumTexture, SpectrumType,
|
||||
};
|
||||
use crate::spectra::{
|
||||
use shared::spectra::{
|
||||
BlackbodySpectrum, ConstantSpectrum, PiecewiseLinearSpectrum, RGB, RGBAlbedoSpectrum,
|
||||
RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, Spectrum,
|
||||
};
|
||||
use crate::utils::error::FileLoc;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::Cell;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use crate::utils::parameters::{ParameterDictionary, ParsedParameter};
|
||||
use flate2::read::GzDecoder;
|
||||
use memmap2::Mmap;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -9,8 +8,9 @@ use std::path::{Path, PathBuf};
|
|||
use std::str::CharIndices;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::utils::parameters::ParsedParameterVector;
|
||||
use crate::{Float, utils::error::FileLoc};
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::parameters::{ParameterDictionary, ParsedParameter, ParsedParameterVector};
|
||||
use shared::Float;
|
||||
|
||||
pub trait ParserTarget {
|
||||
fn identity(&mut self, loc: FileLoc);
|
||||
|
|
|
|||
Loading…
Reference in a new issue