Major overhaul to data management over CPU and GPU, clean up of incorrectly defined light creation methods, a lot of stuff in general. I should make smaller commits.
This commit is contained in:
parent
cda63e42c5
commit
4dbec9bc2c
64 changed files with 3408 additions and 2570 deletions
|
|
@ -6,8 +6,7 @@ edition = "2024"
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
use_f64 = []
|
use_f64 = []
|
||||||
cuda = ["cuda_std", "cust", "cuda_builder", "shared/cuda", ]
|
cuda = ["cust", "cuda_builder", "shared/cuda", ]
|
||||||
use_nvtx = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
|
|
@ -35,6 +34,9 @@ kernels = { path = "kernels" }
|
||||||
|
|
||||||
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
|
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 }
|
cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
|
||||||
|
ptex = "0.3.0"
|
||||||
|
ptex-sys = "0.3.0"
|
||||||
|
slice = "0.0.4"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
|
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
|
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
|
||||||
shared = { path = "../shared" }
|
shared = { path = "../shared", features = ["cuda"] }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,31 @@
|
||||||
use crate::core::camera::{
|
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||||
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform,
|
use crate::core::film::Film;
|
||||||
};
|
|
||||||
use crate::core::film::{Film, FilmTrait};
|
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
use crate::core::medium::Medium;
|
use crate::core::medium::Medium;
|
||||||
use crate::core::options::get_options;
|
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
|
use crate::images::ImageMetadata;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::error::FileLoc;
|
use crate::utils::Transform;
|
||||||
use crate::utils::parameters::ParameterDictionary;
|
|
||||||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
use crate::utils::transform::TransformGeneric;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct OrthographicCamera {
|
pub struct OrthographicCamera {
|
||||||
pub base: CameraBase,
|
pub base: CameraBase,
|
||||||
pub screen_from_camera: TransformGeneric<Float>,
|
pub screen_from_camera: Transform,
|
||||||
pub camera_from_raster: TransformGeneric<Float>,
|
pub camera_from_raster: Transform,
|
||||||
pub raster_from_screen: TransformGeneric<Float>,
|
pub raster_from_screen: Transform,
|
||||||
pub screen_from_raster: TransformGeneric<Float>,
|
pub screen_from_raster: Transform,
|
||||||
pub lens_radius: Float,
|
pub lens_radius: Float,
|
||||||
pub focal_distance: Float,
|
pub focal_distance: Float,
|
||||||
pub dx_camera: Vector3f,
|
pub dx_camera: Vector3f,
|
||||||
pub dy_camera: Vector3f,
|
pub dy_camera: Vector3f,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl OrthographicCamera {
|
impl OrthographicCamera {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
|
|
@ -36,21 +33,30 @@ impl OrthographicCamera {
|
||||||
lens_radius: Float,
|
lens_radius: Float,
|
||||||
focal_distance: Float,
|
focal_distance: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ndc_from_screen: TransformGeneric<Float> = TransformGeneric::scale(
|
let ndc_from_screen: Transform = Transform::scale(
|
||||||
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
||||||
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
||||||
1.,
|
1.,
|
||||||
) * TransformGeneric::translate(
|
) * Transform::translate(Vector3f::new(
|
||||||
Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.),
|
-screen_window.p_min.x(),
|
||||||
);
|
-screen_window.p_max.y(),
|
||||||
let raster_from_ndc = TransformGeneric::scale(
|
0.,
|
||||||
base.film.full_resolution().x() as Float,
|
));
|
||||||
-base.film.full_resolution().y() as Float,
|
let film_ptr = base.film;
|
||||||
|
if film_ptr.is_null() {
|
||||||
|
panic!("Camera must have a film");
|
||||||
|
}
|
||||||
|
let film = unsafe { &*film_ptr };
|
||||||
|
|
||||||
|
let raster_from_ndc = Transform::scale(
|
||||||
|
film.full_resolution().x() as Float,
|
||||||
|
-film.full_resolution().y() as Float,
|
||||||
1.,
|
1.,
|
||||||
);
|
);
|
||||||
|
|
||||||
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
||||||
let screen_from_raster = raster_from_screen.inverse();
|
let screen_from_raster = raster_from_screen.inverse();
|
||||||
let screen_from_camera = TransformGeneric::orthographic(0., 1.);
|
let screen_from_camera = Transform::orthographic(0., 1.);
|
||||||
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
|
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
|
||||||
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
|
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
|
||||||
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
|
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
|
||||||
|
|
@ -71,58 +77,16 @@ impl OrthographicCamera {
|
||||||
dy_camera,
|
dy_camera,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(
|
|
||||||
params: &ParameterDictionary,
|
|
||||||
camera_transform: &CameraTransform,
|
|
||||||
film: Arc<Film>,
|
|
||||||
medium: Medium,
|
|
||||||
loc: &FileLoc,
|
|
||||||
) -> Result<Self, String> {
|
|
||||||
let full_res = film.full_resolution();
|
|
||||||
let camera_params =
|
|
||||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
|
||||||
let base = CameraBase::new(camera_params);
|
|
||||||
let lens_radius = params.get_one_float("lensradius", 0.);
|
|
||||||
let focal_distance = params.get_one_float("focaldistance", 1e6);
|
|
||||||
let frame = params.get_one_float(
|
|
||||||
"frameaspectratio",
|
|
||||||
full_res.x() as Float / full_res.y() as Float,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut screen = if frame > 1. {
|
|
||||||
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
|
||||||
} else {
|
|
||||||
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
|
|
||||||
};
|
|
||||||
|
|
||||||
let sw = params.get_float_array("screenwindow");
|
|
||||||
if !sw.is_empty() {
|
|
||||||
if get_options().fullscreen {
|
|
||||||
eprint!("Screenwindow is ignored in fullscreen mode");
|
|
||||||
} else {
|
|
||||||
if sw.len() == 4 {
|
|
||||||
screen = Bounds2f::from_points(
|
|
||||||
Point2f::new(sw[0], sw[2]),
|
|
||||||
Point2f::new(sw[1], sw[3]),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Err(format!("{}: screenwindow param must have four values", loc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self::new(base, screen, lens_radius, focal_distance))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTrait for OrthographicCamera {
|
impl CameraTrait for OrthographicCamera {
|
||||||
fn base(&self) -> &CameraBase {
|
#[cfg(not(target_os = "cuda"))]
|
||||||
&self.base
|
fn init_metadata(&self, metadata: &mut ImageMetadata) {
|
||||||
|
self.base.init_metadata(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
fn base(&self) -> &CameraBase {
|
||||||
self.base.init_metadata(metadata)
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_ray(
|
fn generate_ray(
|
||||||
|
|
@ -140,18 +104,14 @@ impl CameraTrait for OrthographicCamera {
|
||||||
Some(self.sample_time(sample.time)),
|
Some(self.sample_time(sample.time)),
|
||||||
self.base().medium.clone(),
|
self.base().medium.clone(),
|
||||||
);
|
);
|
||||||
// Modify ray for depth of field
|
|
||||||
if self.lens_radius > 0. {
|
if self.lens_radius > 0. {
|
||||||
// Sample point on lens
|
|
||||||
let p_lens_vec =
|
let p_lens_vec =
|
||||||
self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens));
|
self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens));
|
||||||
let p_lens = Point2f::from(p_lens_vec);
|
let p_lens = Point2f::from(p_lens_vec);
|
||||||
|
|
||||||
// Compute point on plane of focus
|
|
||||||
let ft = self.focal_distance / ray.d.z();
|
let ft = self.focal_distance / ray.d.z();
|
||||||
let p_focus = ray.at(ft);
|
let p_focus = ray.at(ft);
|
||||||
|
|
||||||
// Update ray for effect of lens
|
|
||||||
ray.o = Point3f::new(p_lens.x(), p_lens.y(), 0.);
|
ray.o = Point3f::new(p_lens.x(), p_lens.y(), 0.);
|
||||||
ray.d = (p_focus - ray.o).normalize();
|
ray.d = (p_focus - ray.o).normalize();
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +132,18 @@ impl CameraTrait for OrthographicCamera {
|
||||||
let mut central_cam_ray = self.generate_ray(sample, lambda)?;
|
let mut central_cam_ray = self.generate_ray(sample, lambda)?;
|
||||||
let mut rd = RayDifferential::default();
|
let mut rd = RayDifferential::default();
|
||||||
if self.lens_radius > 0.0 {
|
if self.lens_radius > 0.0 {
|
||||||
return self.generate_ray_differential(sample, lambda);
|
let mut sample_x = sample;
|
||||||
|
sample_x.p_film.x += 1.0;
|
||||||
|
let rx = self.generate_ray(sample_x, lambda)?;
|
||||||
|
|
||||||
|
let mut sample_y = sample;
|
||||||
|
sample_y.p_film.y += 1.0;
|
||||||
|
let ry = self.generate_ray(sample_y, lambda)?;
|
||||||
|
|
||||||
|
rd.rx_origin = rx.ray.o;
|
||||||
|
rd.ry_origin = ry.ray.o;
|
||||||
|
rd.rx_direction = rx.ray.d;
|
||||||
|
rd.ry_direction = ry.ray.d;
|
||||||
} else {
|
} else {
|
||||||
let time = self.sample_time(sample.time);
|
let time = self.sample_time(sample.time);
|
||||||
let world_dx = self
|
let world_dx = self
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,14 @@
|
||||||
use super::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||||
use crate::camera::CameraBaseParameters;
|
use crate::core::film::Film;
|
||||||
use crate::core::film::{Film, FilmTrait};
|
|
||||||
use crate::core::filter::FilterTrait;
|
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
use crate::core::medium::Medium;
|
use crate::core::medium::Medium;
|
||||||
use crate::core::options::get_options;
|
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::error::FileLoc;
|
|
||||||
use crate::utils::parameters::ParameterDictionary;
|
|
||||||
use crate::utils::sampling::sample_uniform_disk_concentric;
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::Transform;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PerspectiveCamera {
|
pub struct PerspectiveCamera {
|
||||||
|
|
@ -30,6 +24,7 @@ pub struct PerspectiveCamera {
|
||||||
pub cos_total_width: Float,
|
pub cos_total_width: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl PerspectiveCamera {
|
impl PerspectiveCamera {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
|
|
@ -80,61 +75,18 @@ impl PerspectiveCamera {
|
||||||
cos_total_width,
|
cos_total_width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(
|
|
||||||
params: &ParameterDictionary,
|
|
||||||
camera_transform: &CameraTransform,
|
|
||||||
film: Arc<Film>,
|
|
||||||
medium: Medium,
|
|
||||||
loc: &FileLoc,
|
|
||||||
) -> Result<Self, String> {
|
|
||||||
let full_res = film.full_resolution();
|
|
||||||
let camera_params =
|
|
||||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
|
||||||
let base = CameraBase::new(camera_params);
|
|
||||||
let lens_radius = params.get_one_float("lensradius", 0.);
|
|
||||||
let focal_distance = params.get_one_float("focaldistance", 1e6);
|
|
||||||
let frame = params.get_one_float(
|
|
||||||
"frameaspectratio",
|
|
||||||
full_res.x() as Float / full_res.y() as Float,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut screen = if frame > 1. {
|
|
||||||
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
|
||||||
} else {
|
|
||||||
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
|
|
||||||
};
|
|
||||||
|
|
||||||
let sw = params.get_float_array("screenwindow");
|
|
||||||
if !sw.is_empty() {
|
|
||||||
if get_options().fullscreen {
|
|
||||||
eprint!("Screenwindow is ignored in fullscreen mode");
|
|
||||||
} else {
|
|
||||||
if sw.len() == 4 {
|
|
||||||
screen = Bounds2f::from_points(
|
|
||||||
Point2f::new(sw[0], sw[2]),
|
|
||||||
Point2f::new(sw[1], sw[3]),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Err(format!("{}: screenwindow param must have four values", loc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let fov = params.get_one_float("fov", 90.);
|
|
||||||
Ok(Self::new(base, fov, screen, lens_radius, focal_distance))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTrait for PerspectiveCamera {
|
impl PerspectiveCamera {
|
||||||
fn base(&self) -> &CameraBase {
|
#[cfg(not(target_os = "cuda"))]
|
||||||
&self.base
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||||
self.base.init_metadata(metadata)
|
self.base.init_metadata(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn base(&self) -> &CameraBase {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_ray(
|
fn generate_ray(
|
||||||
&self,
|
&self,
|
||||||
sample: CameraSample,
|
sample: CameraSample,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
use super::{
|
|
||||||
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, ExitPupilSample,
|
|
||||||
LensElementInterface,
|
|
||||||
};
|
|
||||||
use crate::PI;
|
use crate::PI;
|
||||||
use crate::core::film::{Film, FilmTrait};
|
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||||
|
use crate::core::film::Film;
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
|
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
|
|
@ -11,38 +8,59 @@ use crate::core::medium::Medium;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::core::scattering::refract;
|
use crate::core::scattering::refract;
|
||||||
use crate::image::{Image, PixelFormat};
|
use crate::images::{Image, PixelFormat};
|
||||||
use crate::spectra::color::SRGB;
|
use crate::spectra::color::SRGB;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::error::FileLoc;
|
|
||||||
use crate::utils::file::read_float_file;
|
|
||||||
use crate::utils::math::{lerp, quadratic, square};
|
use crate::utils::math::{lerp, quadratic, square};
|
||||||
use crate::utils::parameters::ParameterDictionary;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct LensElementInterface {
|
||||||
|
pub curvature_radius: Float,
|
||||||
|
pub thickness: Float,
|
||||||
|
pub eta: Float,
|
||||||
|
pub aperture_radius: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct ExitPupilSample {
|
||||||
|
pub p_pupil: Point3f,
|
||||||
|
pub pdf: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXIT_PUPIL_SAMPLES: usize = 64;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct RealisticCamera {
|
pub struct RealisticCamera {
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
focus_distance: Float,
|
focus_distance: Float,
|
||||||
set_aperture_diameter: Float,
|
set_aperture_diameter: Float,
|
||||||
aperture_image: Option<Image>,
|
aperture_image: *const Image,
|
||||||
element_interface: Vec<LensElementInterface>,
|
element_interfaces: *const LensElementInterface,
|
||||||
|
n_elements: usize,
|
||||||
physical_extent: Bounds2f,
|
physical_extent: Bounds2f,
|
||||||
exit_pupil_bounds: Vec<Bounds2f>,
|
exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl RealisticCamera {
|
impl RealisticCamera {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
lens_params: Vec<Float>,
|
lens_params: &[Float],
|
||||||
focus_distance: Float,
|
focus_distance: Float,
|
||||||
set_aperture_diameter: Float,
|
set_aperture_diameter: Float,
|
||||||
aperture_image: Option<Image>,
|
aperture_image: Option<Image>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let aspect =
|
let film_ptr = base.film;
|
||||||
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
|
if film_ptr.is_null() {
|
||||||
let diagonal = base.film.diagonal();
|
panic!("Camera must have a film");
|
||||||
|
}
|
||||||
|
let film = unsafe { &*film_ptr };
|
||||||
|
|
||||||
|
let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float;
|
||||||
|
let diagonal = film.diagonal();
|
||||||
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
|
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
|
||||||
let y = x * aspect;
|
let y = x * aspect;
|
||||||
let physical_extent =
|
let physical_extent =
|
||||||
|
|
@ -73,19 +91,24 @@ impl RealisticCamera {
|
||||||
}
|
}
|
||||||
|
|
||||||
let n_samples = 64;
|
let n_samples = 64;
|
||||||
let half_diag = base.film.diagonal() / 2.0;
|
let half_diag = film.diagonal() / 2.0;
|
||||||
let exit_pupil_bounds: Vec<_> = (0..n_samples)
|
let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
|
||||||
.map(|i| {
|
|
||||||
let r0 = (i as Float / n_samples as Float) * half_diag;
|
for i in 0..EXIT_PUPIL_SAMPLES {
|
||||||
let r1 = ((i + 1) as Float / n_samples as Float) * half_diag;
|
let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
|
||||||
Self::compute_exit_pupil_bounds(&element_interface, r0, r1)
|
let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
|
||||||
})
|
exit_pupil_bounds[i] = Self::compute_exit_pupil_bounds(&element_interface, r0, r1);
|
||||||
.collect();
|
}
|
||||||
|
|
||||||
|
let n_elements = element_interface.len();
|
||||||
|
let element_interfaces = element_interface.as_ptr();
|
||||||
|
std::mem::forget(element_interface);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base,
|
base,
|
||||||
focus_distance,
|
focus_distance,
|
||||||
element_interface,
|
element_interfaces,
|
||||||
|
n_elements,
|
||||||
physical_extent,
|
physical_extent,
|
||||||
set_aperture_diameter,
|
set_aperture_diameter,
|
||||||
aperture_image,
|
aperture_image,
|
||||||
|
|
@ -93,20 +116,58 @@ impl RealisticCamera {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lens_rear_z(&self) -> Float {
|
pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) {
|
||||||
self.element_interface.last().unwrap().thickness
|
let tf = -r_out.o.x() / r_out.d.x();
|
||||||
|
let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x();
|
||||||
|
(-r_out.at(tf).z(), -r_out.at(tp).z())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lens_front_z(&self) -> Float {
|
pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) {
|
||||||
let mut z_sum = 0.;
|
let x = 0.001 * self.get_film().diagonal();
|
||||||
for element in &self.element_interface {
|
let r_scene = Ray::new(
|
||||||
z_sum += element.thickness;
|
Point3f::new(0., x, self.lens_front_z() + 1.),
|
||||||
|
Vector3f::new(0., 0., -1.),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let Some(r_film) = self.trace_lenses_from_film(r_scene) else {
|
||||||
|
panic!(
|
||||||
|
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let (pz0, fz0) = Self::compute_cardinal_points(r_scene, r_film);
|
||||||
|
let r_film = Ray::new(
|
||||||
|
Point3f::new(x, 0., self.lens_rear_z() - 1.),
|
||||||
|
Vector3f::new(0., 0., 1.),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let Some(r_scene) = self.trace_lenses_from_film(r_film) else {
|
||||||
|
panic!(
|
||||||
|
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let (pz1, f_1) = Self::compute_cardinal_points(r_film, r_scene);
|
||||||
|
([pz0, pz1], [fz0, fz1])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_thick_lens(&self, focus_distance: Float) -> Float {
|
||||||
|
let (pz, fz) = self.compute_thick_lens_approximation();
|
||||||
|
let f = fz[0] - pz[0];
|
||||||
|
let z = -focus_distance;
|
||||||
|
let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0]);
|
||||||
|
if c <= 0 {
|
||||||
|
panic!(
|
||||||
|
"Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration",
|
||||||
|
focusDistance
|
||||||
|
);
|
||||||
}
|
}
|
||||||
z_sum
|
let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.;
|
||||||
|
self.element_interface.last().thickness + delta
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rear_element_radius(&self) -> Float {
|
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
||||||
self.element_interface.last().unwrap().aperture_radius
|
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_exit_pupil_bounds(
|
fn compute_exit_pupil_bounds(
|
||||||
|
|
@ -160,38 +221,40 @@ impl RealisticCamera {
|
||||||
pupil_bounds
|
pupil_bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
|
||||||
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
|
// Find exit pupil bound for sample distance from film center
|
||||||
}
|
let film = self.film();
|
||||||
|
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
|
||||||
|
let mut r_index = (r_film / (film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
|
||||||
|
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
|
||||||
|
|
||||||
pub fn intersect_spherical_element(
|
let pupil_bounds = self.exit_pupil_bounds[r_index];
|
||||||
radius: Float,
|
if pupil_bounds.is_degenerate() {
|
||||||
z_center: Float,
|
|
||||||
ray: &Ray,
|
|
||||||
) -> Option<(Float, Normal3f)> {
|
|
||||||
let o = ray.o - Vector3f::new(0.0, 0.0, z_center);
|
|
||||||
|
|
||||||
let a = ray.d.norm_squared();
|
|
||||||
let b = 2.0 * ray.d.dot(o.into());
|
|
||||||
let c = Vector3f::from(o).norm_squared() - radius * radius;
|
|
||||||
|
|
||||||
let (t0, t1) = quadratic(a, b, c)?;
|
|
||||||
|
|
||||||
let use_closer_t = (ray.d.z() > 0.0) ^ (radius < 0.0);
|
|
||||||
let t = if use_closer_t { t0.min(t1) } else { t0.max(t1) };
|
|
||||||
|
|
||||||
// Intersection is behind the ray's origin.
|
|
||||||
if t < 0.0 {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let p_hit_relative = o + ray.d * t;
|
// Generate sample point inside exit pupil bound
|
||||||
// Ensures the normal points towards the incident ray.
|
let p_lens = pupil_bounds.lerp(u_lens);
|
||||||
let n = Normal3f::from(Vector3f::from(p_hit_relative))
|
let pdf = 1. / pupil_bounds.area();
|
||||||
.normalize()
|
|
||||||
.face_forward(-ray.d);
|
|
||||||
|
|
||||||
Some((t, n))
|
// Return sample point rotated by angle of _pFilm_ with $+x$ axis
|
||||||
|
let sin_theta = if r_film != 0. {
|
||||||
|
p_film.y() / r_film
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
};
|
||||||
|
let cos_theta = if r_film != 0. {
|
||||||
|
p_film.x() / r_film
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
};
|
||||||
|
let p_pupil = Point3f::new(
|
||||||
|
cos_theta * p_lens.x() - sin_theta * p_lens.y(),
|
||||||
|
sin_theta * p_lens.x() + cos_theta * p_lens.y(),
|
||||||
|
self.lens_rear_z(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(ExitPupilSample { p_pupil, pdf })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trace_lenses_from_film(&self, r_camera: &Ray) -> Option<(Float, Ray)> {
|
pub fn trace_lenses_from_film(&self, r_camera: &Ray) -> Option<(Float, Ray)> {
|
||||||
|
|
@ -274,229 +337,72 @@ impl RealisticCamera {
|
||||||
Some((weight, r_out))
|
Some((weight, r_out))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
|
fn intersect_spherical_element(
|
||||||
// Find exit pupil bound for sample distance from film center
|
radius: Float,
|
||||||
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
|
z_center: Float,
|
||||||
let mut r_index =
|
ray: &Ray,
|
||||||
(r_film / (self.base.film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
|
) -> Option<(Float, Normal3f)> {
|
||||||
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
|
let o = ray.o - Vector3f::new(0.0, 0.0, z_center);
|
||||||
|
|
||||||
let pupil_bounds = self.exit_pupil_bounds[r_index];
|
let a = ray.d.norm_squared();
|
||||||
if pupil_bounds.is_degenerate() {
|
let b = 2.0 * ray.d.dot(o.into());
|
||||||
|
let c = Vector3f::from(o).norm_squared() - radius * radius;
|
||||||
|
|
||||||
|
let (t0, t1) = quadratic(a, b, c)?;
|
||||||
|
|
||||||
|
let use_closer_t = (ray.d.z() > 0.0) ^ (radius < 0.0);
|
||||||
|
let t = if use_closer_t { t0.min(t1) } else { t0.max(t1) };
|
||||||
|
|
||||||
|
// Intersection is behind the ray's origin.
|
||||||
|
if t < 0.0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate sample point inside exit pupil bound
|
let p_hit_relative = o + ray.d * t;
|
||||||
let p_lens = pupil_bounds.lerp(u_lens);
|
// Ensures the normal points towards the incident ray.
|
||||||
let pdf = 1. / pupil_bounds.area();
|
let n = Normal3f::from(Vector3f::from(p_hit_relative))
|
||||||
|
.normalize()
|
||||||
|
.face_forward(-ray.d);
|
||||||
|
|
||||||
// Return sample point rotated by angle of _pFilm_ with $+x$ axis
|
Some((t, n))
|
||||||
let sin_theta = if r_film != 0. {
|
|
||||||
p_film.y() / r_film
|
|
||||||
} else {
|
|
||||||
0.
|
|
||||||
};
|
|
||||||
let cos_theta = if r_film != 0. {
|
|
||||||
p_film.x() / r_film
|
|
||||||
} else {
|
|
||||||
1.
|
|
||||||
};
|
|
||||||
let p_pupil = Point3f::new(
|
|
||||||
cos_theta * p_lens.x() - sin_theta * p_lens.y(),
|
|
||||||
sin_theta * p_lens.x() + cos_theta * p_lens.y(),
|
|
||||||
self.lens_rear_z(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(ExitPupilSample { p_pupil, pdf })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(
|
pub fn lens_rear_z(&self) -> Float {
|
||||||
params: &ParameterDictionary,
|
self.element_interface.last().unwrap().thickness
|
||||||
camera_transform: &CameraTransform,
|
}
|
||||||
film: Arc<Film>,
|
|
||||||
medium: Medium,
|
|
||||||
loc: &FileLoc,
|
|
||||||
) -> Result<Self, String> {
|
|
||||||
let camera_params =
|
|
||||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
|
||||||
let base = CameraBase::new(camera_params);
|
|
||||||
let aperture_diameter = params.get_one_float("aperturediameter", 1.);
|
|
||||||
let focal_distance = params.get_one_float("focaldistance", 10.);
|
|
||||||
let lens_file = params.get_one_string("lensfile", "");
|
|
||||||
|
|
||||||
if lens_file.is_empty() {
|
pub fn lens_front_z(&self) -> Float {
|
||||||
return Err(format!("{}: No lens file supplied", loc));
|
let mut z_sum = 0.;
|
||||||
|
for element in &self.element_interface {
|
||||||
|
z_sum += element.thickness;
|
||||||
}
|
}
|
||||||
|
z_sum
|
||||||
|
}
|
||||||
|
|
||||||
let lens_params = read_float_file(lens_file.as_str()).map_err(|e| e.to_string())?;
|
pub fn rear_element_radius(&self) -> Float {
|
||||||
if lens_params.len() % 4 != 0 {
|
self.element_interface.last().unwrap().aperture_radius
|
||||||
return Err(format!(
|
|
||||||
"{}: excess values in lens specification file; must be multiple-of-four values, read {}",
|
|
||||||
loc,
|
|
||||||
lens_params.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let builtin_res = 256;
|
|
||||||
let rasterize = |vert: &[Point2f]| -> Image {
|
|
||||||
let mut image = Image::new(
|
|
||||||
PixelFormat::F32,
|
|
||||||
Point2i::new(builtin_res, builtin_res),
|
|
||||||
&["Y"],
|
|
||||||
SRGB,
|
|
||||||
);
|
|
||||||
|
|
||||||
let res = image.resolution();
|
|
||||||
for y in 0..res.y() {
|
|
||||||
for x in 0..res.x() {
|
|
||||||
// Map pixel to [-1, 1] range
|
|
||||||
let p = Point2f::new(
|
|
||||||
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
|
|
||||||
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut winding_number = 0;
|
|
||||||
// Winding number test against edges
|
|
||||||
for i in 0..vert.len() {
|
|
||||||
let i1 = (i + 1) % vert.len();
|
|
||||||
let v_i = vert[i];
|
|
||||||
let v_i1 = vert[i1];
|
|
||||||
|
|
||||||
let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y())
|
|
||||||
- (p.y() - v_i.y()) * (v_i1.x() - v_i.x());
|
|
||||||
|
|
||||||
if v_i.y() <= p.y() {
|
|
||||||
if v_i1.y() > p.y() && e > 0.0 {
|
|
||||||
winding_number += 1;
|
|
||||||
}
|
|
||||||
} else if v_i1.y() <= p.y() && e < 0.0 {
|
|
||||||
winding_number -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image.set_channel(
|
|
||||||
Point2i::new(x, y),
|
|
||||||
0,
|
|
||||||
if winding_number == 0 { 0.0 } else { 1.0 },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
image
|
|
||||||
};
|
|
||||||
|
|
||||||
let aperture_name = params.get_one_string("aperture", "");
|
|
||||||
let mut aperture_image: Option<Image> = None;
|
|
||||||
|
|
||||||
if !aperture_name.is_empty() {
|
|
||||||
match aperture_name.as_str() {
|
|
||||||
"gaussian" => {
|
|
||||||
let mut img = Image::new(
|
|
||||||
PixelFormat::F32,
|
|
||||||
Point2i::new(builtin_res, builtin_res),
|
|
||||||
&["Y"],
|
|
||||||
SRGB,
|
|
||||||
);
|
|
||||||
let res = img.resolution();
|
|
||||||
for y in 0..res.y() {
|
|
||||||
for x in 0..res.x() {
|
|
||||||
let uv = Point2f::new(
|
|
||||||
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
|
|
||||||
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
|
|
||||||
);
|
|
||||||
let r2 = square(uv.x()) + square(uv.y());
|
|
||||||
let sigma2 = 1.0;
|
|
||||||
let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0);
|
|
||||||
img.set_channel(Point2i::new(x, y), 0, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aperture_image = Some(img);
|
|
||||||
}
|
|
||||||
"square" => {
|
|
||||||
let mut img = Image::new(
|
|
||||||
PixelFormat::F32,
|
|
||||||
Point2i::new(builtin_res, builtin_res),
|
|
||||||
&["Y"],
|
|
||||||
SRGB,
|
|
||||||
);
|
|
||||||
let low = (0.25 * builtin_res as Float) as i32;
|
|
||||||
let high = (0.75 * builtin_res as Float) as i32;
|
|
||||||
for y in low..high {
|
|
||||||
for x in low..high {
|
|
||||||
img.set_channel(Point2i::new(x, y), 0, 4.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aperture_image = Some(img);
|
|
||||||
}
|
|
||||||
"pentagon" => {
|
|
||||||
let c1 = (5.0f32.sqrt() - 1.0) / 4.0;
|
|
||||||
let c2 = (5.0f32.sqrt() + 1.0) / 4.0;
|
|
||||||
let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
|
|
||||||
let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
|
|
||||||
let mut vert = [
|
|
||||||
Point2f::new(0.0, 1.0),
|
|
||||||
Point2f::new(s1, c1),
|
|
||||||
Point2f::new(s2, -c2),
|
|
||||||
Point2f::new(-s2, -c2),
|
|
||||||
Point2f::new(-s1, c1),
|
|
||||||
];
|
|
||||||
for v in vert.iter_mut() {
|
|
||||||
*v = Point2f::from(Vector2f::from(*v) * 0.8);
|
|
||||||
}
|
|
||||||
aperture_image = Some(rasterize(&vert));
|
|
||||||
}
|
|
||||||
"star" => {
|
|
||||||
let mut vert = Vec::with_capacity(10);
|
|
||||||
for i in 0..10 {
|
|
||||||
let r = if i % 2 == 1 {
|
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
(72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos())
|
|
||||||
};
|
|
||||||
let angle = PI * i as Float / 5.0;
|
|
||||||
vert.push(Point2f::new(r * angle.cos(), r * angle.sin()));
|
|
||||||
}
|
|
||||||
vert.reverse();
|
|
||||||
aperture_image = Some(rasterize(&vert));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if let Ok(im) = Image::read(Path::new(&aperture_name), None) {
|
|
||||||
if im.image.n_channels() > 1 {
|
|
||||||
let mut mono =
|
|
||||||
Image::new(PixelFormat::F32, im.image.resolution(), &["Y"], SRGB);
|
|
||||||
let res = mono.resolution();
|
|
||||||
for y in 0..res.y() {
|
|
||||||
for x in 0..res.x() {
|
|
||||||
let avg =
|
|
||||||
im.image.get_channels_default(Point2i::new(x, y)).average();
|
|
||||||
mono.set_channel(Point2i::new(x, y), 0, avg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aperture_image = Some(mono);
|
|
||||||
} else {
|
|
||||||
aperture_image = Some(im.image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self::new(
|
|
||||||
base,
|
|
||||||
lens_params,
|
|
||||||
focal_distance,
|
|
||||||
aperture_diameter,
|
|
||||||
aperture_image,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTrait for RealisticCamera {
|
impl CameraTrait for RealisticCamera {
|
||||||
|
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||||
|
self.base.init_metadata(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
fn base(&self) -> &CameraBase {
|
fn base(&self) -> &CameraBase {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
fn get_film(&self) -> &Film {
|
||||||
self.base.init_metadata(metadata)
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
{
|
||||||
|
if self.base.film.is_null() {
|
||||||
|
panic!(
|
||||||
|
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { &*self.base.film }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_ray(
|
fn generate_ray(
|
||||||
|
|
@ -505,9 +411,10 @@ impl CameraTrait for RealisticCamera {
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
) -> Option<CameraRay> {
|
) -> Option<CameraRay> {
|
||||||
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
|
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
|
||||||
|
let film = self.get_film();
|
||||||
let s = Point2f::new(
|
let s = Point2f::new(
|
||||||
sample.p_film.x() / self.base.film.full_resolution().x() as Float,
|
sample.p_film.x() / film.full_resolution().x() as Float,
|
||||||
sample.p_film.y() / self.base.film.full_resolution().y() as Float,
|
sample.p_film.y() / film.full_resolution().y() as Float,
|
||||||
);
|
);
|
||||||
let p_film2 = self.physical_extent.lerp(s);
|
let p_film2 = self.physical_extent.lerp(s);
|
||||||
let p_film = Point3f::new(-p_film2.x(), p_film2.y(), 0.);
|
let p_film = Point3f::new(-p_film2.x(), p_film2.y(), 0.);
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,30 @@
|
||||||
use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform};
|
use crate::core::camera::{CameraBase, CameraRay, CameraTransform};
|
||||||
use crate::core::film::{Film, FilmTrait};
|
use crate::core::film::Film;
|
||||||
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
|
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
|
||||||
use crate::core::medium::Medium;
|
use crate::core::medium::Medium;
|
||||||
use crate::core::options::get_options;
|
|
||||||
use crate::core::pbrt::{Float, PI};
|
use crate::core::pbrt::{Float, PI};
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::error::FileLoc;
|
|
||||||
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
|
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
|
||||||
use crate::utils::parameters::ParameterDictionary;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum Mapping {
|
pub enum Mapping {
|
||||||
EquiRectangular,
|
EquiRectangular,
|
||||||
EqualArea,
|
EqualArea,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct SphericalCamera {
|
pub struct SphericalCamera {
|
||||||
pub base: CameraBase,
|
|
||||||
pub screen: Bounds2f,
|
|
||||||
pub lens_radius: Float,
|
|
||||||
pub focal_distance: Float,
|
|
||||||
pub mapping: Mapping,
|
pub mapping: Mapping,
|
||||||
|
pub base: CameraBase,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl SphericalCamera {
|
impl SphericalCamera {
|
||||||
pub fn create(
|
pub fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||||
params: &ParameterDictionary,
|
self.base.init_metadata(metadata)
|
||||||
camera_transform: &CameraTransform,
|
|
||||||
film: Arc<Film>,
|
|
||||||
medium: Medium,
|
|
||||||
loc: &FileLoc,
|
|
||||||
) -> Result<Self, String> {
|
|
||||||
let full_res = film.full_resolution();
|
|
||||||
let camera_params =
|
|
||||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
|
||||||
let base = CameraBase::new(camera_params);
|
|
||||||
let lens_radius = params.get_one_float("lensradius", 0.);
|
|
||||||
let focal_distance = params.get_one_float("focaldistance", 1e30);
|
|
||||||
let frame = params.get_one_float(
|
|
||||||
"frameaspectratio",
|
|
||||||
full_res.x() as Float / full_res.y() as Float,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut screen = if frame > 1. {
|
|
||||||
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
|
||||||
} else {
|
|
||||||
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
|
|
||||||
};
|
|
||||||
|
|
||||||
let sw = params.get_float_array("screenwindow");
|
|
||||||
if !sw.is_empty() {
|
|
||||||
if get_options().fullscreen {
|
|
||||||
eprint!("Screenwindow is ignored in fullscreen mode");
|
|
||||||
} else {
|
|
||||||
if sw.len() == 4 {
|
|
||||||
screen = Bounds2f::from_points(
|
|
||||||
Point2f::new(sw[0], sw[2]),
|
|
||||||
Point2f::new(sw[1], sw[3]),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Err(format!("{}: screenwindow param must have four values", loc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let m = params.get_one_string("mapping", "equalarea");
|
|
||||||
let mapping = match m.as_str() {
|
|
||||||
"equal_area" => Mapping::EqualArea,
|
|
||||||
"equirectangular" => Mapping::EquiRectangular,
|
|
||||||
_ => {
|
|
||||||
return Err(format!(
|
|
||||||
"{}: unknown mapping for spherical camera at {}",
|
|
||||||
m, loc
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
mapping,
|
|
||||||
base,
|
|
||||||
screen,
|
|
||||||
lens_radius,
|
|
||||||
focal_distance,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,8 +33,16 @@ impl CameraTrait for SphericalCamera {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
fn get_film(&self) -> &Film {
|
||||||
self.base.init_metadata(metadata)
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
{
|
||||||
|
if self.base.film.is_null() {
|
||||||
|
panic!(
|
||||||
|
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { &*self.base.film }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_ray(
|
fn generate_ray(
|
||||||
|
|
@ -104,9 +51,10 @@ impl CameraTrait for SphericalCamera {
|
||||||
_lamdba: &SampledWavelengths,
|
_lamdba: &SampledWavelengths,
|
||||||
) -> Option<CameraRay> {
|
) -> Option<CameraRay> {
|
||||||
// Compute spherical camera ray direction
|
// Compute spherical camera ray direction
|
||||||
|
let film = self.get_film();
|
||||||
let mut uv = Point2f::new(
|
let mut uv = Point2f::new(
|
||||||
sample.p_film.x() / self.base().film.full_resolution().x() as Float,
|
sample.p_film.x() / film.full_resolution().x() as Float,
|
||||||
sample.p_film.y() / self.base().film.full_resolution().y() as Float,
|
sample.p_film.y() / film.full_resolution().y() as Float,
|
||||||
);
|
);
|
||||||
let dir: Vector3f;
|
let dir: Vector3f;
|
||||||
if self.mapping == Mapping::EquiRectangular {
|
if self.mapping == Mapping::EquiRectangular {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use std::cmp::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum SplitMethod {
|
pub enum SplitMethod {
|
||||||
SAH,
|
SAH,
|
||||||
|
|
@ -18,10 +19,11 @@ pub enum SplitMethod {
|
||||||
EqualCounts,
|
EqualCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
struct BVHSplitBucket {
|
struct BVHSplitBucket {
|
||||||
count: usize,
|
pub count: usize,
|
||||||
bounds: Bounds3f,
|
pub bounds: Bounds3f,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::core::film::{Film, FilmTrait};
|
use crate::cameras::*;
|
||||||
|
use crate::core::film::Film;
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
|
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
|
|
@ -9,34 +10,31 @@ use crate::core::pbrt::Float;
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::images::ImageMetadata;
|
use crate::images::ImageMetadata;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::error::FileLoc;
|
|
||||||
use crate::utils::math::lerp;
|
use crate::utils::math::lerp;
|
||||||
use crate::utils::parameters::ParameterDictionary;
|
|
||||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
use crate::utils::transform::{AnimatedTransform, Transform};
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct CameraRay {
|
pub struct CameraRay {
|
||||||
pub ray: Ray,
|
pub ray: Ray,
|
||||||
pub weight: SampledSpectrum,
|
pub weight: SampledSpectrum,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct CameraWiSample {
|
pub struct CameraWiSample {
|
||||||
wi_spec: SampledSpectrum,
|
pub wi_spec: SampledSpectrum,
|
||||||
wi: Vector3f,
|
pub wi: Vector3f,
|
||||||
pdf: Float,
|
pub pdf: Float,
|
||||||
p_raster: Point2f,
|
pub p_raster: Point2f,
|
||||||
p_ref: Interaction,
|
pub p_ref: Interaction,
|
||||||
p_lens: Interaction,
|
pub p_lens: Interaction,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct CameraTransform {
|
pub struct CameraTransform {
|
||||||
pub render_from_camera: AnimatedTransform,
|
pub render_from_camera: AnimatedTransform,
|
||||||
pub world_from_render: Transform,
|
pub world_from_render: Transform,
|
||||||
|
|
@ -106,55 +104,20 @@ impl CameraTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct CameraBaseParameters {
|
|
||||||
pub camera_transform: CameraTransform,
|
|
||||||
pub shutter_open: Float,
|
|
||||||
pub shutter_close: Float,
|
|
||||||
pub film: Arc<Film>,
|
|
||||||
pub medium_id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CameraBaseParameters {
|
|
||||||
pub fn new(
|
|
||||||
camera_transform: &CameraTransform,
|
|
||||||
film: Arc<Film>,
|
|
||||||
medium: Arc<Medium>,
|
|
||||||
params: &ParameterDictionary,
|
|
||||||
loc: &FileLoc,
|
|
||||||
) -> Self {
|
|
||||||
let mut shutter_open = params.get_one_float("shutteropen", 0.);
|
|
||||||
let mut shutter_close = params.get_one_float("shutterclose", 1.);
|
|
||||||
if shutter_close < shutter_open {
|
|
||||||
eprint!(
|
|
||||||
"{}: Shutter close time {} < shutter open {}. Swapping",
|
|
||||||
loc, shutter_close, shutter_open
|
|
||||||
);
|
|
||||||
std::mem::swap(&mut shutter_open, &mut shutter_close);
|
|
||||||
}
|
|
||||||
CameraBaseParameters {
|
|
||||||
camera_transform: Arc::new(camera_transform.clone()),
|
|
||||||
shutter_open,
|
|
||||||
shutter_close,
|
|
||||||
film,
|
|
||||||
medium_id: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CameraBase {
|
pub struct CameraBase {
|
||||||
pub camera_transform: CameraTransform,
|
pub camera_transform: CameraTransform,
|
||||||
pub shutter_open: Float,
|
pub shutter_open: Float,
|
||||||
pub shutter_close: Float,
|
pub shutter_close: Float,
|
||||||
pub film: Arc<Film>,
|
pub film: *const Film,
|
||||||
pub medium: Option<Arc<Medium>>,
|
pub medium: *const Medium,
|
||||||
pub min_pos_differential_x: Vector3f,
|
pub min_pos_differential_x: Vector3f,
|
||||||
pub min_pos_differential_y: Vector3f,
|
pub min_pos_differential_y: Vector3f,
|
||||||
pub min_dir_differential_x: Vector3f,
|
pub min_dir_differential_x: Vector3f,
|
||||||
pub min_dir_differential_y: Vector3f,
|
pub min_dir_differential_y: Vector3f,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl CameraBase {
|
impl CameraBase {
|
||||||
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
|
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
|
||||||
let camera_from_world: Transform =
|
let camera_from_world: Transform =
|
||||||
|
|
@ -162,83 +125,97 @@ impl CameraBase {
|
||||||
|
|
||||||
metadata.camera_from_world = Some(camera_from_world.get_matrix());
|
metadata.camera_from_world = Some(camera_from_world.get_matrix());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(p: CameraBaseParameters) -> Self {
|
#[enum_dispatch(CameraTrait)]
|
||||||
Self {
|
#[repr(C)]
|
||||||
camera_transform: p.camera_transform.as_ref().clone(),
|
#[derive(Debug, Copy, Clone)]
|
||||||
shutter_open: p.shutter_open,
|
pub enum Camera {
|
||||||
shutter_close: p.shutter_close,
|
Perspective(PerspectiveCamera),
|
||||||
film: p.film.clone(),
|
Orthographic(OrthographicCamera),
|
||||||
medium: p.medium,
|
Spherical(SphericalCamera),
|
||||||
min_pos_differential_x: Vector3f::default(),
|
Realistic(RealisticCamera),
|
||||||
min_pos_differential_y: Vector3f::default(),
|
|
||||||
min_dir_differential_x: Vector3f::default(),
|
|
||||||
min_dir_differential_y: Vector3f::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait CameraTrait {
|
pub trait CameraTrait {
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn init_metadata(&self, metadata: &mut ImageMetadata);
|
||||||
|
|
||||||
fn base(&self) -> &CameraBase;
|
fn base(&self) -> &CameraBase;
|
||||||
fn get_film(&self) -> &Film {
|
|
||||||
&self.base().film
|
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn get_film(&self) -> Result<&Film, String> {
|
||||||
|
if self.film.is_null() {
|
||||||
|
return Err("Camera error: Film pointer is null.".to_string());
|
||||||
|
}
|
||||||
|
Ok(unsafe { &*self.film })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_time(&self, u: Float) -> Float {
|
||||||
|
lerp(u, self.base().shutter_open, self.base().shutter_close)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolution(&self) -> Point2i {
|
fn resolution(&self) -> Point2i {
|
||||||
self.base().film.full_resolution()
|
self.base().film.full_resolution()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_metadata(&self, metadata: &mut ImageMetadata);
|
fn render_from_camera(&self, r: &Ray, t_max: &mut Option<Float>) -> Ray {
|
||||||
|
self.base()
|
||||||
|
.camera_transform
|
||||||
|
.render_from_camera_ray(r, t_max)
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
|
|
||||||
fn generate_ray_differential(
|
fn generate_ray_differential(
|
||||||
&self,
|
&self,
|
||||||
sample: CameraSample,
|
sample: CameraSample,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
) -> Option<CameraRay> {
|
) -> Option<CameraRay> {
|
||||||
let mut central_cam_ray = self.generate_ray(sample, lambda)?;
|
match self {
|
||||||
let mut rd = RayDifferential::default();
|
Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
|
||||||
let mut rx_found = false;
|
_ => {
|
||||||
let mut ry_found = false;
|
let mut central_cam_ray = self.generate_ray(sample, lambda)?;
|
||||||
|
let mut rd = RayDifferential::default();
|
||||||
|
let mut rx_found = false;
|
||||||
|
let mut ry_found = false;
|
||||||
|
|
||||||
for eps in [0.05, -0.05] {
|
for eps in [0.05, -0.05] {
|
||||||
let mut s_shift = sample;
|
let mut s_shift = sample;
|
||||||
s_shift.p_film[0] += eps;
|
s_shift.p_film[0] += eps;
|
||||||
|
|
||||||
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
|
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||||
rd.rx_origin =
|
rd.rx_origin = central_cam_ray.ray.o
|
||||||
central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
+ (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||||
rd.rx_direction =
|
rd.rx_direction = central_cam_ray.ray.d
|
||||||
central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
+ (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||||
rx_found = true;
|
rx_found = true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for eps in [0.05, -0.05] {
|
||||||
|
let mut s_shift = sample;
|
||||||
|
s_shift.p_film[1] += eps;
|
||||||
|
|
||||||
|
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||||
|
rd.ry_origin = central_cam_ray.ray.o
|
||||||
|
+ (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||||
|
rd.ry_direction = central_cam_ray.ray.d
|
||||||
|
+ (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||||
|
ry_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rx_found && ry_found {
|
||||||
|
central_cam_ray.ray.differential = Some(rd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(central_cam_ray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for eps in [0.05, -0.05] {
|
|
||||||
let mut s_shift = sample;
|
|
||||||
s_shift.p_film[1] += eps;
|
|
||||||
|
|
||||||
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
|
|
||||||
rd.ry_origin =
|
|
||||||
central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
|
||||||
rd.ry_direction =
|
|
||||||
central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
|
||||||
ry_found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rx_found && ry_found {
|
|
||||||
central_cam_ray.ray.differential = Some(rd);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(central_cam_ray)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample_time(&self, u: Float) -> Float {
|
|
||||||
lerp(u, self.base().shutter_open, self.base().shutter_close)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn approximate_dp_dxy(
|
fn approximate_dp_dxy(
|
||||||
|
|
@ -290,65 +267,4 @@ pub trait CameraTrait {
|
||||||
time,
|
time,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_from_camera(&self, r: &Ray, t_max: &mut Option<Float>) -> Ray {
|
|
||||||
self.base()
|
|
||||||
.camera_transform
|
|
||||||
.render_from_camera_ray(r, t_max)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[enum_dispatch(CameraTrait)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Camera {
|
|
||||||
Perspective(PerspectiveCamera),
|
|
||||||
Orthographic(OrthographicCamera),
|
|
||||||
Spherical(SphericalCamera),
|
|
||||||
Realistic(RealisticCamera),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Camera {
|
|
||||||
pub fn create(
|
|
||||||
name: &str,
|
|
||||||
params: &ParameterDictionary,
|
|
||||||
medium: Medium,
|
|
||||||
camera_transform: &CameraTransform,
|
|
||||||
film: Arc<Film>,
|
|
||||||
loc: &FileLoc,
|
|
||||||
) -> Result<Self, String> {
|
|
||||||
match name {
|
|
||||||
"perspective" => {
|
|
||||||
let camera =
|
|
||||||
PerspectiveCamera::create(params, camera_transform, film, medium, loc)?;
|
|
||||||
Ok(Camera::Perspective(camera))
|
|
||||||
}
|
|
||||||
"orthographic" => {
|
|
||||||
let camera =
|
|
||||||
OrthographicCamera::create(params, camera_transform, film, medium, loc)?;
|
|
||||||
Ok(Camera::Orthographic(camera))
|
|
||||||
}
|
|
||||||
"realistic" => {
|
|
||||||
let camera = RealisticCamera::create(params, camera_transform, film, medium, loc)?;
|
|
||||||
Ok(Camera::Realistic(camera))
|
|
||||||
}
|
|
||||||
"spherical" => {
|
|
||||||
let camera = SphericalCamera::create(params, camera_transform, film, medium, loc)?;
|
|
||||||
Ok(Camera::Spherical(camera))
|
|
||||||
}
|
|
||||||
_ => Err(format!("Camera type '{}' unknown at {}", name, loc)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LensElementInterface {
|
|
||||||
pub curvature_radius: Float,
|
|
||||||
pub thickness: Float,
|
|
||||||
pub eta: Float,
|
|
||||||
pub aperture_radius: Float,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ExitPupilSample {
|
|
||||||
pub p_pupil: Point3f,
|
|
||||||
pub pdf: Float,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,12 @@ use std::ops::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::core::geometry::Point2f;
|
use crate::core::geometry::Point2f;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::{Float, find_interval};
|
||||||
use crate::spectra::Spectrum;
|
use crate::core::spectrum::Spectrum;
|
||||||
use crate::utils::math::{SquareMatrix, evaluate_polynomial, lerp};
|
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
pub trait Triplet {
|
|
||||||
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct XYZ {
|
pub struct XYZ {
|
||||||
pub x: Float,
|
pub x: Float,
|
||||||
|
|
@ -23,9 +18,9 @@ pub struct XYZ {
|
||||||
pub z: Float,
|
pub z: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Triplet for XYZ {
|
impl From<(Float, Float, Float)> for XYZ {
|
||||||
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self {
|
fn from(triplet: (Float, Float, Float)) -> Self {
|
||||||
XYZ::new(c1, c2, c3)
|
XYZ::new(triplet.0, triplet.1, triplet.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,9 +254,9 @@ pub struct RGB {
|
||||||
pub b: Float,
|
pub b: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Triplet for RGB {
|
impl From<(Float, Float, Float)> for RGB {
|
||||||
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self {
|
fn from(triplet: (Float, Float, Float)) -> Self {
|
||||||
RGB::new(c1, c2, c3)
|
RGB::new(triplet.0, triplet.1, triplet.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -467,7 +462,7 @@ impl fmt::Display for RGB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mul<XYZ> for SquareMatrix<Float, 3> {
|
impl Mul<XYZ> for SquareMatrix3f {
|
||||||
type Output = RGB;
|
type Output = RGB;
|
||||||
|
|
||||||
fn mul(self, v: XYZ) -> RGB {
|
fn mul(self, v: XYZ) -> RGB {
|
||||||
|
|
@ -478,7 +473,7 @@ impl Mul<XYZ> for SquareMatrix<Float, 3> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mul<RGB> for SquareMatrix<Float, 3> {
|
impl Mul<RGB> for SquareMatrix3f {
|
||||||
type Output = XYZ;
|
type Output = XYZ;
|
||||||
fn mul(self, v: RGB) -> XYZ {
|
fn mul(self, v: RGB) -> XYZ {
|
||||||
let x = self[0][0] * v.r + self[0][1] * v.g + self[0][2] * v.b;
|
let x = self[0][0] * v.r + self[0][1] * v.g + self[0][2] * v.b;
|
||||||
|
|
@ -493,7 +488,7 @@ pub trait MatrixMulColor {
|
||||||
fn mul_xyz(&self, v: XYZ) -> XYZ;
|
fn mul_xyz(&self, v: XYZ) -> XYZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatrixMulColor for SquareMatrix<Float, 3> {
|
impl MatrixMulColor for SquareMatrix3f {
|
||||||
fn mul_rgb(&self, v: RGB) -> RGB {
|
fn mul_rgb(&self, v: RGB) -> RGB {
|
||||||
let m = self;
|
let m = self;
|
||||||
RGB::new(
|
RGB::new(
|
||||||
|
|
@ -513,65 +508,22 @@ impl MatrixMulColor for SquareMatrix<Float, 3> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const RES: usize = 64;
|
#[repr(C)]
|
||||||
pub type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3];
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RGBToSpectrumTable {
|
|
||||||
coeffs: &'static [Float],
|
|
||||||
scale: &'static [Float],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RGBToSpectrumTable {
|
|
||||||
pub fn srgb() -> Self {
|
|
||||||
Self {
|
|
||||||
coeffs: *crate::data::SRGB_COEFFS,
|
|
||||||
scale: *crate::data::SRGB_SCALE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dci_p3() -> Self {
|
|
||||||
Self {
|
|
||||||
coeffs: *crate::data::DCI_P3_COEFFS,
|
|
||||||
scale: *crate::data::DCI_P3_SCALE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rec2020() -> Self {
|
|
||||||
Self {
|
|
||||||
coeffs: *crate::data::REC2020_COEFFS,
|
|
||||||
scale: *crate::data::REC2020_SCALE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn aces2065_1() -> Self {
|
|
||||||
Self {
|
|
||||||
coeffs: *crate::data::ACES_COEFFS,
|
|
||||||
scale: *crate::data::ACES_SCALE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct RGBSigmoidPolynomial {
|
pub struct RGBSigmoidPolynomial {
|
||||||
c0: Float,
|
pub c0: Float,
|
||||||
c1: Float,
|
pub c1: Float,
|
||||||
c2: Float,
|
pub c2: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBSigmoidPolynomial {
|
impl RGBSigmoidPolynomial {
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new(c0: Float, c1: Float, c2: Float) -> Self {
|
pub fn new(c0: Float, c1: Float, c2: Float) -> Self {
|
||||||
Self { c0, c1, c2 }
|
Self { c0, c1, c2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate(&self, lambda: Float) -> Float {
|
pub fn evaluate(&self, lambda: Float) -> Float {
|
||||||
let eval = match evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) {
|
let eval = evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]);
|
||||||
Some(value) => value,
|
|
||||||
None => {
|
|
||||||
panic!("evaluate_polynomial returned None with non-empty coefficients")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::s(eval)
|
Self::s(eval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,120 +538,16 @@ impl RGBSigmoidPolynomial {
|
||||||
|
|
||||||
fn s(x: Float) -> Float {
|
fn s(x: Float) -> Float {
|
||||||
if x.is_infinite() {
|
if x.is_infinite() {
|
||||||
if x > 0.0 { return 1.0 } else { return 0.0 }
|
if x > 0.0 {
|
||||||
|
return 1.0;
|
||||||
|
} else {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
0.5 + x / (2.0 * (1.0 + (x * x)).sqrt())
|
0.5 + x / (2.0 * (1.0 + (x * x)).sqrt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBToSpectrumTable {
|
|
||||||
pub fn new(scale: &'static [Float], coeffs: &'static [Float]) -> Self {
|
|
||||||
Self { scale, coeffs }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
|
||||||
//TODO: Check all of this logic, this is important
|
|
||||||
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
|
|
||||||
return RGBSigmoidPolynomial::new(
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
(rgb[0] - 0.5) / (rgb[0] * (1.0 - rgb[0])).sqrt(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_c = rgb.max_component_value();
|
|
||||||
let c = [rgb.r, rgb.g, rgb.b];
|
|
||||||
let (min_c, mid_c) = if c[0] < c[1] {
|
|
||||||
if c[1] < c[2] {
|
|
||||||
(c[0], c[1])
|
|
||||||
}
|
|
||||||
// 0 < 1 < 2
|
|
||||||
else if c[0] < c[2] {
|
|
||||||
(c[0], c[2])
|
|
||||||
}
|
|
||||||
// 0 < 2 < 1
|
|
||||||
else {
|
|
||||||
(c[2], c[0])
|
|
||||||
} // 2 < 0 < 1
|
|
||||||
} else {
|
|
||||||
if c[1] > c[2] {
|
|
||||||
(c[2], c[1])
|
|
||||||
}
|
|
||||||
// 2 < 1 < 0
|
|
||||||
else if c[0] > c[2] {
|
|
||||||
(c[1], c[2])
|
|
||||||
}
|
|
||||||
// 1 < 2 < 0
|
|
||||||
else {
|
|
||||||
(c[1], c[0])
|
|
||||||
} // 1 < 0 < 2
|
|
||||||
};
|
|
||||||
|
|
||||||
let z = min_c / max_c;
|
|
||||||
let x = mid_c / max_c;
|
|
||||||
|
|
||||||
let z_float = z * (RES - 1) as Float;
|
|
||||||
let zi = (z_float as usize).min(RES - 2);
|
|
||||||
let z_t = z_float - zi as Float;
|
|
||||||
let x_float = x * (RES - 1) as Float;
|
|
||||||
let xi = (x_float as usize).min(RES - 2);
|
|
||||||
let x_t = x_float - xi as Float;
|
|
||||||
|
|
||||||
let mut coeffs = [0.0; 3];
|
|
||||||
#[allow(clippy::needless_range_loop)]
|
|
||||||
for i in 0..3 {
|
|
||||||
let offset = |dz, dx| (((zi + dz) * RES + (xi + dx)) * RES + (RES - 1)) * 3 + i;
|
|
||||||
let c00 = self.coeffs[offset(0, 0)];
|
|
||||||
let c01 = self.coeffs[offset(0, 1)];
|
|
||||||
let c10 = self.coeffs[offset(1, 0)];
|
|
||||||
let c11 = self.coeffs[offset(1, 1)];
|
|
||||||
let v0 = lerp(x_t, c00, c01);
|
|
||||||
let v1 = lerp(x_t, c10, c11);
|
|
||||||
coeffs[i] = lerp(z_t, v0, v1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let scale_float = max_c * (RES - 1) as Float;
|
|
||||||
let si = (scale_float as usize).min(RES - 2);
|
|
||||||
let s_t = scale_float - si as Float;
|
|
||||||
|
|
||||||
let scale = lerp(s_t, self.scale[si], self.scale[si + 1]);
|
|
||||||
|
|
||||||
RGBSigmoidPolynomial {
|
|
||||||
c0: coeffs[0],
|
|
||||||
c1: coeffs[1],
|
|
||||||
c2: coeffs[2],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const LMS_FROM_XYZ: SquareMatrix<Float, 3> = SquareMatrix::new([
|
|
||||||
[0.8951, 0.2664, -0.1614],
|
|
||||||
[-0.7502, 1.7135, 0.0367],
|
|
||||||
[0.0389, -0.0685, 1.0296],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const XYZ_FROM_LMS: SquareMatrix<Float, 3> = SquareMatrix::new([
|
|
||||||
[0.986993, -0.147054, 0.159963],
|
|
||||||
[0.432305, 0.51836, 0.0492912],
|
|
||||||
[-0.00852866, 0.0400428, 0.968487],
|
|
||||||
]);
|
|
||||||
|
|
||||||
pub fn white_balance(src_white: Point2f, target_white: Point2f) -> SquareMatrix<Float, 3> {
|
|
||||||
// Find LMS coefficients for source and target white
|
|
||||||
let src_xyz = XYZ::from_xyy(src_white, None);
|
|
||||||
let dst_xyz = XYZ::from_xyy(target_white, None);
|
|
||||||
let src_lms = LMS_FROM_XYZ * src_xyz;
|
|
||||||
let dst_lms = LMS_FROM_XYZ * dst_xyz;
|
|
||||||
|
|
||||||
// Return white balancing matrix for source and target white
|
|
||||||
let lms_correct = SquareMatrix::<Float, 3>::diag(&[
|
|
||||||
dst_lms[0] / src_lms[0],
|
|
||||||
dst_lms[1] / src_lms[1],
|
|
||||||
dst_lms[2] / src_lms[2],
|
|
||||||
]);
|
|
||||||
XYZ_FROM_LMS * lms_correct * LMS_FROM_XYZ
|
|
||||||
}
|
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display {
|
pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display {
|
||||||
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]);
|
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]);
|
||||||
|
|
@ -710,6 +558,7 @@ pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[enum_dispatch(ColorEncodingTrait)]
|
#[enum_dispatch(ColorEncodingTrait)]
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub enum ColorEncoding {
|
pub enum ColorEncoding {
|
||||||
|
|
@ -723,6 +572,7 @@ impl fmt::Display for ColorEncoding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub struct LinearEncoding;
|
pub struct LinearEncoding;
|
||||||
impl ColorEncodingTrait for LinearEncoding {
|
impl ColorEncodingTrait for LinearEncoding {
|
||||||
|
|
@ -747,6 +597,7 @@ impl fmt::Display for LinearEncoding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub struct SRGBEncoding;
|
pub struct SRGBEncoding;
|
||||||
impl ColorEncodingTrait for SRGBEncoding {
|
impl ColorEncodingTrait for SRGBEncoding {
|
||||||
|
|
@ -1045,3 +896,122 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [
|
||||||
0.9911022186,
|
0.9911022186,
|
||||||
1.0000000000,
|
1.0000000000,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const RES: usize = 64;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Coeffs {
|
||||||
|
pub c0: Float,
|
||||||
|
pub c1: Float,
|
||||||
|
pub c2: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Coeffs {
|
||||||
|
type Output = Self;
|
||||||
|
#[inline(always)]
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
c0: self.c0 + rhs.c0,
|
||||||
|
c1: self.c1 + rhs.c1,
|
||||||
|
c2: self.c2 + rhs.c2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Float> for Coeffs {
|
||||||
|
type Output = Self;
|
||||||
|
#[inline(always)]
|
||||||
|
fn mul(self, s: Float) -> Self {
|
||||||
|
Self {
|
||||||
|
c0: self.c0 * s,
|
||||||
|
c1: self.c1 * s,
|
||||||
|
c2: self.c2 * s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct RGBToSpectrumTable {
|
||||||
|
pub z_nodes: *const Float,
|
||||||
|
pub coeffs: *const Coeffs,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for RGBToSpectrumTable {}
|
||||||
|
unsafe impl Sync for RGBToSpectrumTable {}
|
||||||
|
|
||||||
|
impl RGBToSpectrumTable {
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_coeffs(&self, bucket: usize, z: usize, y: usize, x: usize) -> Coeffs {
|
||||||
|
let offset = bucket * (RES * RES * RES) + z * (RES * RES) + y * (RES) + x;
|
||||||
|
unsafe { *self.coeffs.add(offset) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
||||||
|
let m = rgb.max_component_value();
|
||||||
|
let min_val = rgb.min_component_value();
|
||||||
|
if m - min_val < 1e-4 {
|
||||||
|
let x = clamp(rgb[0], 1e-4, 0.9999);
|
||||||
|
let c2 = (0.5 - x) / (x * (1.0 - x)).sqrt();
|
||||||
|
return RGBSigmoidPolynomial::new(0.0, 0.0, c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the primary bucket (c) based on the dominant axis
|
||||||
|
let c_idx = if rgb[0] > rgb[1] {
|
||||||
|
if rgb[0] > rgb[2] { 0 } else { 2 }
|
||||||
|
} else if rgb[1] > rgb[2] {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
};
|
||||||
|
|
||||||
|
let z = m;
|
||||||
|
let (x_val, y_val) = match c_idx {
|
||||||
|
0 => (rgb[1], rgb[2]), // R is max -> G, B
|
||||||
|
1 => (rgb[0], rgb[2]), // G is max -> R, B
|
||||||
|
_ => (rgb[0], rgb[1]), // B is max -> R, G
|
||||||
|
};
|
||||||
|
|
||||||
|
let (coord_a, coord_b) = if x_val > y_val {
|
||||||
|
(y_val, x_val)
|
||||||
|
} else {
|
||||||
|
(x_val, y_val)
|
||||||
|
};
|
||||||
|
let x = coord_a / z;
|
||||||
|
let y = coord_b / z;
|
||||||
|
|
||||||
|
let z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, RES) };
|
||||||
|
let zi = find_interval(RES, |i| z_nodes_slice[i] < z);
|
||||||
|
let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]);
|
||||||
|
let x_float = x * (RES - 1) as Float;
|
||||||
|
let xi = (x_float as usize).min(RES - 2);
|
||||||
|
let dx = x_float - xi as Float;
|
||||||
|
|
||||||
|
let y_float = y * (RES - 1) as Float;
|
||||||
|
let yi = (y_float as usize).min(RES - 2);
|
||||||
|
let dy = y_float - yi as Float;
|
||||||
|
|
||||||
|
let c000 = self.get_coeffs(c_idx, zi, yi, xi);
|
||||||
|
let c001 = self.get_coeffs(c_idx, zi, yi, xi + 1);
|
||||||
|
let c010 = self.get_coeffs(c_idx, zi, yi + 1, xi);
|
||||||
|
let c011 = self.get_coeffs(c_idx, zi, yi + 1, xi + 1);
|
||||||
|
let c100 = self.get_coeffs(c_idx, zi + 1, yi, xi);
|
||||||
|
let c101 = self.get_coeffs(c_idx, zi + 1, yi, xi + 1);
|
||||||
|
let c110 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi);
|
||||||
|
let c111 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi + 1);
|
||||||
|
let c00 = lerp(dx, c000, c001);
|
||||||
|
let c01 = lerp(dx, c010, c011);
|
||||||
|
let c10 = lerp(dx, c100, c101);
|
||||||
|
let c11 = lerp(dx, c110, c111);
|
||||||
|
let c0 = lerp(dy, c00, c01);
|
||||||
|
let c1 = lerp(dy, c10, c11);
|
||||||
|
let c = lerp(dz, c0, c1);
|
||||||
|
|
||||||
|
RGBSigmoidPolynomial {
|
||||||
|
c0: c.c0,
|
||||||
|
c1: c.c1,
|
||||||
|
c2: c.c2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::core::camera::CameraTransform;
|
use crate::core::camera::CameraTransform;
|
||||||
|
use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
|
||||||
use crate::core::filter::Filter;
|
use crate::core::filter::Filter;
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
|
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
|
||||||
|
|
@ -6,13 +7,12 @@ use crate::core::geometry::{
|
||||||
};
|
};
|
||||||
use crate::core::interaction::SurfaceInteraction;
|
use crate::core::interaction::SurfaceInteraction;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
|
||||||
use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
|
use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
|
||||||
use crate::spectra::color::{MatrixMulColor, SRGB, Triplet, white_balance};
|
|
||||||
use crate::spectra::data::generate_cie_d;
|
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
||||||
PiecewiseLinearSpectrum, RGB, RGBColorSpace, SampledSpectrum, SampledWavelengths, Spectrum,
|
PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
|
||||||
SpectrumTrait, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum,
|
get_named_spectrum,
|
||||||
};
|
};
|
||||||
use crate::utils::AtomicFloat;
|
use crate::utils::AtomicFloat;
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
|
|
@ -20,7 +20,6 @@ use crate::utils::math::linear_least_squares;
|
||||||
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
|
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
|
||||||
use crate::utils::sampling::VarianceEstimator;
|
use crate::utils::sampling::VarianceEstimator;
|
||||||
use crate::utils::transform::AnimatedTransform;
|
use crate::utils::transform::AnimatedTransform;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|
@ -30,7 +29,7 @@ pub struct RGBFilm {
|
||||||
pub write_fp16: bool,
|
pub write_fp16: bool,
|
||||||
pub filter_integral: Float,
|
pub filter_integral: Float,
|
||||||
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||||
pub pixels: Arc<Array2D<RGBPixel>>,
|
pub pixels: Array2D<RGBPixel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|
@ -53,7 +52,7 @@ impl RGBFilm {
|
||||||
if sensor_ptr.is_null() {
|
if sensor_ptr.is_null() {
|
||||||
panic!("Film must have a sensor");
|
panic!("Film must have a sensor");
|
||||||
}
|
}
|
||||||
let sensor = unsafe { &*self.sensor };
|
let sensor = unsafe { &*sensor_ptr };
|
||||||
let filter_integral = base.filter.integral();
|
let filter_integral = base.filter.integral();
|
||||||
let sensor_matrix = sensor.xyz_from_sensor_rgb;
|
let sensor_matrix = sensor.xyz_from_sensor_rgb;
|
||||||
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
|
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
|
||||||
|
|
@ -75,7 +74,7 @@ impl RGBFilm {
|
||||||
write_fp16,
|
write_fp16,
|
||||||
filter_integral,
|
filter_integral,
|
||||||
output_rgbf_from_sensor_rgb,
|
output_rgbf_from_sensor_rgb,
|
||||||
pixels: Arc::new(pixels_array),
|
pixels: std::sync::Arc::new(pixels_array),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -89,12 +88,16 @@ impl RGBFilm {
|
||||||
&mut self.base
|
&mut self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "cuda"))]
|
pub fn get_sensor(&self) -> &PixelSensor {
|
||||||
pub fn get_sensor(&self) -> Result<&PixelSensor, String> {
|
#[cfg(not(target_os = "cuda"))]
|
||||||
if self.sensor.is_null() {
|
{
|
||||||
return Err("FilmBase error: PixelSensor pointer is null.".to_string());
|
if self.sensor.is_null() {
|
||||||
|
panic!(
|
||||||
|
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(unsafe { &*self.sensor })
|
unsafe { &*self.sensor }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_sample(
|
pub fn add_sample(
|
||||||
|
|
@ -259,27 +262,20 @@ impl GBufferFilm {
|
||||||
&mut self.base
|
&mut self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "cuda"))]
|
pub fn get_sensor(&self) -> &PixelSensor {
|
||||||
pub fn get_sensor(&self) -> Result<&PixelSensor, String> {
|
|
||||||
if self.sensor.is_null() {
|
|
||||||
return Err("FilmBase error: PixelSensor pointer is null.".to_string());
|
|
||||||
}
|
|
||||||
Ok(unsafe { &*self.sensor })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn get_sensor(&self) -> &PixelSensor {
|
|
||||||
#[cfg(not(target_os = "cuda"))]
|
#[cfg(not(target_os = "cuda"))]
|
||||||
{
|
{
|
||||||
if self.sensor.is_null() {
|
if self.sensor.is_null() {
|
||||||
panic!("FilmBase: PixelSensor pointer is null");
|
panic!(
|
||||||
|
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
unsafe { &*self.sensor }
|
||||||
&*self.sensor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
|
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
|
||||||
let sensor = unsafe { self.get_pixel_sensor() };
|
let sensor = unsafe { self.get_sensor() };
|
||||||
let mut rgb = sensor.to_sensor_rgb(l, lambda);
|
let mut rgb = sensor.to_sensor_rgb(l, lambda);
|
||||||
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||||
if m > self.max_component_value {
|
if m > self.max_component_value {
|
||||||
|
|
@ -310,7 +306,7 @@ impl GBufferFilm {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
|
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
|
||||||
let sensor = unsafe { self.get_pixel_sensor() };
|
let sensor = unsafe { self.get_sensor() };
|
||||||
let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
|
let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
|
||||||
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
|
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
|
||||||
}
|
}
|
||||||
|
|
@ -467,15 +463,15 @@ pub struct PixelSensor {
|
||||||
pub imaging_ratio: Float,
|
pub imaging_ratio: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "cuda"))]
|
|
||||||
impl PixelSensor {
|
impl PixelSensor {
|
||||||
const N_SWATCH_REFLECTANCES: usize = 24;
|
const N_SWATCH_REFLECTANCES: usize = 24;
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
r: Spectrum,
|
r: Spectrum,
|
||||||
g: Spectrum,
|
g: Spectrum,
|
||||||
b: Spectrum,
|
b: Spectrum,
|
||||||
output_colorspace: RGBColorSpace,
|
output_colorspace: RGBColorSpace,
|
||||||
sensor_illum: Option<Arc<Spectrum>>,
|
sensor_illum: Option<std::sync::Arc<Spectrum>>,
|
||||||
imaging_ratio: Float,
|
imaging_ratio: Float,
|
||||||
swatches: &[Spectrum; 24],
|
swatches: &[Spectrum; 24],
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
|
@ -490,11 +486,11 @@ impl PixelSensor {
|
||||||
let r_bar = DenselySampledSpectrum::from_spectrum(&r);
|
let r_bar = DenselySampledSpectrum::from_spectrum(&r);
|
||||||
let g_bar = DenselySampledSpectrum::from_spectrum(&g);
|
let g_bar = DenselySampledSpectrum::from_spectrum(&g);
|
||||||
let b_bar = DenselySampledSpectrum::from_spectrum(&b);
|
let b_bar = DenselySampledSpectrum::from_spectrum(&b);
|
||||||
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
let mut rgb_camera = [[0.; 3]; Self::N_SWATCH_REFLECTANCES];
|
||||||
|
|
||||||
let swatches = Self::get_swatches();
|
let swatches = Self::get_swatches();
|
||||||
|
|
||||||
for i in 0..N_SWATCH_REFLECTANCES {
|
for i in 0..Self::N_SWATCH_REFLECTANCES {
|
||||||
let rgb = Self::project_reflectance::<RGB>(
|
let rgb = Self::project_reflectance::<RGB>(
|
||||||
&swatches[i],
|
&swatches[i],
|
||||||
illum,
|
illum,
|
||||||
|
|
@ -507,10 +503,10 @@ impl PixelSensor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
let mut xyz_output = [[0.; 3]; Self::N_SWATCH_REFLECTANCES];
|
||||||
let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
|
let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
|
||||||
let sensor_white_y = illum.inner_product(cie_y());
|
let sensor_white_y = illum.inner_product(cie_y());
|
||||||
for i in 0..N_SWATCH_REFLECTANCES {
|
for i in 0..Self::N_SWATCH_REFLECTANCES {
|
||||||
let s = swatches[i].clone();
|
let s = swatches[i].clone();
|
||||||
let xyz = Self::project_reflectance::<XYZ>(
|
let xyz = Self::project_reflectance::<XYZ>(
|
||||||
&s,
|
&s,
|
||||||
|
|
@ -537,7 +533,7 @@ impl PixelSensor {
|
||||||
|
|
||||||
pub fn new_with_white_balance(
|
pub fn new_with_white_balance(
|
||||||
output_colorspace: &RGBColorSpace,
|
output_colorspace: &RGBColorSpace,
|
||||||
sensor_illum: Option<Arc<Spectrum>>,
|
sensor_illum: Option<std::sync::Arc<Spectrum>>,
|
||||||
imaging_ratio: Float,
|
imaging_ratio: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x());
|
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x());
|
||||||
|
|
@ -562,13 +558,16 @@ impl PixelSensor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project_reflectance<T: Triplet>(
|
pub fn project_reflectance<T>(
|
||||||
refl: &Spectrum,
|
refl: &Spectrum,
|
||||||
illum: &Spectrum,
|
illum: &Spectrum,
|
||||||
b1: &Spectrum,
|
b1: &Spectrum,
|
||||||
b2: &Spectrum,
|
b2: &Spectrum,
|
||||||
b3: &Spectrum,
|
b3: &Spectrum,
|
||||||
) -> T {
|
) -> T
|
||||||
|
where
|
||||||
|
T: From<[Float; 3]>,
|
||||||
|
{
|
||||||
let mut result = [0.; 3];
|
let mut result = [0.; 3];
|
||||||
let mut g_integral = 0.;
|
let mut g_integral = 0.;
|
||||||
|
|
||||||
|
|
@ -590,7 +589,7 @@ impl PixelSensor {
|
||||||
result[2] *= inv_g;
|
result[2] *= inv_g;
|
||||||
}
|
}
|
||||||
|
|
||||||
T::from_triplet(result[0], result[1], result[2])
|
T::from((result[0], result[1], result[2]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,7 +651,7 @@ pub enum Film {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Film {
|
impl Film {
|
||||||
fn base(&self) -> &FilmBase {
|
pub fn base(&self) -> &FilmBase {
|
||||||
match self {
|
match self {
|
||||||
Film::RGB(f) => f.base(),
|
Film::RGB(f) => f.base(),
|
||||||
Film::GBuffer(f) => f.base(),
|
Film::GBuffer(f) => f.base(),
|
||||||
|
|
@ -660,7 +659,7 @@ impl Film {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base(&mut self) -> &mut FilmBase {
|
pub fn base_mut(&mut self) -> &mut FilmBase {
|
||||||
match self {
|
match self {
|
||||||
Film::RGB(f) => f.base_mut(),
|
Film::RGB(f) => f.base_mut(),
|
||||||
Film::GBuffer(f) => f.base_mut(),
|
Film::GBuffer(f) => f.base_mut(),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::filters::*;
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
|
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
|
||||||
use crate::utils::sampling::PiecewiseConstant2D;
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
|
|
@ -12,13 +13,13 @@ pub struct FilterSample {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FilterSampler {
|
pub struct FilterSampler {
|
||||||
domain: Bounds2f,
|
pub domain: Bounds2f,
|
||||||
distrib: PiecewiseConstant2D,
|
pub distrib: PiecewiseConstant2D,
|
||||||
f: Array2D<Float>,
|
pub f: Array2D<Float>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "cuda"))]
|
|
||||||
impl FilterSampler {
|
impl FilterSampler {
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new<F>(radius: Vector2f, func: F) -> Self
|
pub fn new<F>(radius: Vector2f, func: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(Point2f) -> Float,
|
F: Fn(Point2f) -> Float,
|
||||||
|
|
@ -44,9 +45,7 @@ impl FilterSampler {
|
||||||
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
||||||
Self { domain, f, distrib }
|
Self { domain, f, distrib }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl FilterSampler {
|
|
||||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||||
let (p, pdf, pi) = self.distrib.sample(u);
|
let (p, pdf, pi) = self.distrib.sample(u);
|
||||||
|
|
||||||
|
|
@ -59,6 +58,15 @@ impl FilterSampler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait FilterTrait {
|
||||||
|
fn radius(&self) -> Vector2f;
|
||||||
|
fn evaluate(&self, p: Point2f) -> Float;
|
||||||
|
fn integral(&self) -> Float;
|
||||||
|
fn sample(&self, u: Point2f) -> FilterSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[enum_dispatch(FilterTrait)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Filter {
|
pub enum Filter {
|
||||||
Box(BoxFilter),
|
Box(BoxFilter),
|
||||||
|
|
@ -67,44 +75,3 @@ pub enum Filter {
|
||||||
LanczosSinc(LanczosSincFilter),
|
LanczosSinc(LanczosSincFilter),
|
||||||
Triangle(TriangleFilter),
|
Triangle(TriangleFilter),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Filter {
|
|
||||||
pub fn radius(&self) -> Vector2f {
|
|
||||||
match self {
|
|
||||||
Filter::Box(f) => f.radius(),
|
|
||||||
Filter::Gaussian(f) => f.radius(),
|
|
||||||
Filter::Mitchell(f) => f.radius(),
|
|
||||||
Filter::LanczosSinc(f) => f.radius(),
|
|
||||||
Filter::Triangle(f) => f.radius(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evaluate(&self, p: Point2f) -> Float {
|
|
||||||
match self {
|
|
||||||
Filter::Box(f) => f.evaluate(p),
|
|
||||||
Filter::Gaussian(f) => f.evaluate(p),
|
|
||||||
Filter::Mitchell(f) => f.evaluate(p),
|
|
||||||
Filter::LanczosSinc(f) => f.evaluate(p),
|
|
||||||
Filter::Triangle(f) => f.evaluate(p),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn integral(&self) -> Float {
|
|
||||||
match self {
|
|
||||||
Filter::Box(f) => f.integral(),
|
|
||||||
Filter::Gaussian(f) => f.integral(),
|
|
||||||
Filter::Mitchell(f) => f.integral(),
|
|
||||||
Filter::LanczosSinc(f) => f.integral(),
|
|
||||||
Filter::Triangle(f) => f.integral(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
|
||||||
match self {
|
|
||||||
Filter::Box(f) => f.sample(u),
|
|
||||||
Filter::Gaussian(f) => f.sample(u),
|
|
||||||
Filter::Mitchell(f) => f.sample(u),
|
|
||||||
Filter::LanczosSinc(f) => f.sample(u),
|
|
||||||
Filter::Triangle(f) => f.sample(u),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@ use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
|
||||||
use crate::core::medium::Medium;
|
use crate::core::medium::Medium;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::math::{next_float_down, next_float_up};
|
use crate::utils::math::{next_float_down, next_float_up};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
pub o: Point3f,
|
pub o: Point3f,
|
||||||
pub d: Vector3f,
|
pub d: Vector3f,
|
||||||
pub medium: Option<Arc<Medium>>,
|
pub medium: *const Medium,
|
||||||
pub time: Float,
|
pub time: Float,
|
||||||
// We do this instead of creating a trait for Rayable or some gnarly thing like that
|
// We do this instead of creating a trait for Rayable or some gnarly thing like that
|
||||||
pub differential: Option<RayDifferential>,
|
pub differential: *const RayDifferential,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Ray {
|
impl Default for Ray {
|
||||||
|
|
@ -27,7 +27,7 @@ impl Default for Ray {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ray {
|
impl Ray {
|
||||||
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: Option<Arc<Medium>>) -> Self {
|
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: *const Medium) -> Self {
|
||||||
Self {
|
Self {
|
||||||
o,
|
o,
|
||||||
d,
|
d,
|
||||||
|
|
@ -110,6 +110,7 @@ impl Ray {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct RayDifferential {
|
pub struct RayDifferential {
|
||||||
pub rx_origin: Point3f,
|
pub rx_origin: Point3f,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::camera::{Camera, CameraTrait};
|
|
||||||
use crate::core::bssrdf::BSSRDF;
|
use crate::core::bssrdf::BSSRDF;
|
||||||
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
|
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
|
||||||
|
use crate::core::camera::Camera;
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
|
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
|
||||||
};
|
};
|
||||||
|
use crate::core::light::Light;
|
||||||
use crate::core::material::{
|
use crate::core::material::{
|
||||||
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
|
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
|
||||||
};
|
};
|
||||||
|
|
@ -11,26 +12,24 @@ use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
|
||||||
use crate::core::options::get_options;
|
use crate::core::options::get_options;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::sampler::{Sampler, SamplerTrait};
|
use crate::core::sampler::{Sampler, SamplerTrait};
|
||||||
use crate::core::texture::{FloatTexture, UniversalTextureEvaluator};
|
use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
|
||||||
use crate::image::Image;
|
use crate::images::Image;
|
||||||
use crate::lights::{Light, LightTrait};
|
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::math::{clamp, difference_of_products, square};
|
use crate::utils::math::{clamp, difference_of_products, square};
|
||||||
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Default, Copy, Clone, Debug)]
|
||||||
pub struct InteractionData {
|
pub struct InteractionData {
|
||||||
pub pi: Point3fi,
|
pub pi: Point3fi,
|
||||||
pub n: Normal3f,
|
pub n: Normal3f,
|
||||||
pub time: Float,
|
pub time: Float,
|
||||||
pub wo: Vector3f,
|
pub wo: Vector3f,
|
||||||
pub medium_interface: Option<MediumInterface>,
|
pub medium_interface: MediumInterface,
|
||||||
pub medium: Option<Arc<Medium>>,
|
pub medium: *const Medium,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
|
|
@ -63,16 +62,16 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> {
|
fn get_medium(&self, w: Vector3f) -> *const Medium {
|
||||||
let data = self.get_common();
|
let data = self.get_common();
|
||||||
if let Some(mi) = &data.medium_interface {
|
if let Some(mi) = &data.medium_interface {
|
||||||
if w.dot(data.n.into()) > 0.0 {
|
if w.dot(data.n.into()) > 0.0 {
|
||||||
mi.outside.clone()
|
mi.outside
|
||||||
} else {
|
} else {
|
||||||
mi.inside.clone()
|
mi.inside
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data.medium.clone()
|
data.medium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,9 +90,8 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
|
||||||
ray
|
ray
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_ray_to_interaction(&self, other: &dyn InteractionTrait) -> Ray {
|
fn spawn_ray_to_interaction(&self, other: InteractionData) -> Ray {
|
||||||
let data = self.get_common();
|
let data = self.get_common();
|
||||||
let other_data = other.get_common();
|
|
||||||
|
|
||||||
let mut ray =
|
let mut ray =
|
||||||
Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n);
|
Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n);
|
||||||
|
|
@ -110,8 +108,9 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[enum_dispatch(InteractionTrait)]
|
#[enum_dispatch(InteractionTrait)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Interaction {
|
pub enum Interaction {
|
||||||
Surface(SurfaceInteraction),
|
Surface(SurfaceInteraction),
|
||||||
Medium(MediumInteraction),
|
Medium(MediumInteraction),
|
||||||
|
|
@ -128,6 +127,7 @@ impl Interaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SimpleInteraction {
|
pub struct SimpleInteraction {
|
||||||
pub common: InteractionData,
|
pub common: InteractionData,
|
||||||
|
|
@ -171,8 +171,9 @@ impl InteractionTrait for SimpleInteraction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[repr(C)]
|
||||||
pub struct Shadinggeom {
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
|
pub struct ShadingGeom {
|
||||||
pub n: Normal3f,
|
pub n: Normal3f,
|
||||||
pub dpdu: Vector3f,
|
pub dpdu: Vector3f,
|
||||||
pub dpdv: Vector3f,
|
pub dpdv: Vector3f,
|
||||||
|
|
@ -180,7 +181,8 @@ pub struct Shadinggeom {
|
||||||
pub dndv: Normal3f,
|
pub dndv: Normal3f,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
pub struct SurfaceInteraction {
|
pub struct SurfaceInteraction {
|
||||||
pub common: InteractionData,
|
pub common: InteractionData,
|
||||||
pub uv: Point2f,
|
pub uv: Point2f,
|
||||||
|
|
@ -188,19 +190,22 @@ pub struct SurfaceInteraction {
|
||||||
pub dpdv: Vector3f,
|
pub dpdv: Vector3f,
|
||||||
pub dndu: Normal3f,
|
pub dndu: Normal3f,
|
||||||
pub dndv: Normal3f,
|
pub dndv: Normal3f,
|
||||||
pub shading: Shadinggeom,
|
pub shading: ShadingGeom,
|
||||||
pub face_index: usize,
|
pub face_index: u32,
|
||||||
pub area_light: Option<Arc<Light>>,
|
pub area_light: *const Light,
|
||||||
pub material: Option<Arc<Material>>,
|
pub material: *const Material,
|
||||||
|
pub shape: *const Shape,
|
||||||
pub dpdx: Vector3f,
|
pub dpdx: Vector3f,
|
||||||
pub dpdy: Vector3f,
|
pub dpdy: Vector3f,
|
||||||
pub dudx: Float,
|
pub dudx: Float,
|
||||||
pub dvdx: Float,
|
pub dvdx: Float,
|
||||||
pub dudy: Float,
|
pub dudy: Float,
|
||||||
pub dvdy: Float,
|
pub dvdy: Float,
|
||||||
pub shape: Arc<Shape>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for SurfaceInteraction {}
|
||||||
|
unsafe impl Sync for SurfaceInteraction {}
|
||||||
|
|
||||||
impl SurfaceInteraction {
|
impl SurfaceInteraction {
|
||||||
pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
if let Some(area_light) = &self.area_light {
|
if let Some(area_light) = &self.area_light {
|
||||||
|
|
@ -304,6 +309,7 @@ impl SurfaceInteraction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn get_bsdf(
|
pub fn get_bsdf(
|
||||||
&mut self,
|
&mut self,
|
||||||
r: &Ray,
|
r: &Ray,
|
||||||
|
|
@ -343,12 +349,12 @@ impl SurfaceInteraction {
|
||||||
Some(bsdf)
|
Some(bsdf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn get_bssrdf(
|
pub fn get_bssrdf(
|
||||||
&self,
|
&self,
|
||||||
_ray: &Ray,
|
_ray: &Ray,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
_camera: &Camera,
|
_camera: &Camera,
|
||||||
_scratch: &Bump,
|
|
||||||
) -> Option<BSSRDF> {
|
) -> Option<BSSRDF> {
|
||||||
let material = {
|
let material = {
|
||||||
let root_mat = self.material.as_deref()?;
|
let root_mat = self.material.as_deref()?;
|
||||||
|
|
@ -370,8 +376,8 @@ impl SurfaceInteraction {
|
||||||
fn compute_bump_geom(
|
fn compute_bump_geom(
|
||||||
&mut self,
|
&mut self,
|
||||||
tex_eval: &UniversalTextureEvaluator,
|
tex_eval: &UniversalTextureEvaluator,
|
||||||
displacement: Option<FloatTexture>,
|
displacement: *const GPUFloatTexture,
|
||||||
normal_image: Option<Arc<Image>>,
|
normal_image: *const Image,
|
||||||
) {
|
) {
|
||||||
let ctx = NormalBumpEvalContext::from(&*self);
|
let ctx = NormalBumpEvalContext::from(&*self);
|
||||||
let (dpdu, dpdv) = if let Some(disp) = displacement {
|
let (dpdu, dpdv) = if let Some(disp) = displacement {
|
||||||
|
|
@ -494,12 +500,12 @@ impl InteractionTrait for SurfaceInteraction {
|
||||||
&mut self.common
|
&mut self.common
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> {
|
fn get_medium(&self, w: Vector3f) -> *const Medium {
|
||||||
self.common.medium_interface.as_ref().and_then(|interface| {
|
self.common.medium_interface.as_ref().and_then(|interface| {
|
||||||
if self.n().dot(w.into()) > 0.0 {
|
if self.n().dot(w.into()) > 0.0 {
|
||||||
interface.outside.clone()
|
interface.outside
|
||||||
} else {
|
} else {
|
||||||
interface.inside.clone()
|
interface.inside
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -543,7 +549,7 @@ impl SurfaceInteraction {
|
||||||
dpdv,
|
dpdv,
|
||||||
dndu,
|
dndu,
|
||||||
dndv,
|
dndv,
|
||||||
shading: Shadinggeom {
|
shading: ShadingGeom {
|
||||||
n: shading_n,
|
n: shading_n,
|
||||||
dpdu,
|
dpdu,
|
||||||
dpdv,
|
dpdv,
|
||||||
|
|
@ -559,7 +565,7 @@ impl SurfaceInteraction {
|
||||||
dudy: 0.0,
|
dudy: 0.0,
|
||||||
dvdx: 0.0,
|
dvdx: 0.0,
|
||||||
dvdy: 0.0,
|
dvdy: 0.0,
|
||||||
shape: Arc::new(Shape::default()),
|
shape: core::ptr::null(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -625,31 +631,32 @@ impl SurfaceInteraction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn set_intersection_properties(
|
pub fn set_intersection_properties(
|
||||||
&mut self,
|
&mut self,
|
||||||
mtl: Arc<Material>,
|
mtl: *const Material,
|
||||||
area: Arc<Light>,
|
area: *const Light,
|
||||||
prim_medium_interface: Option<MediumInterface>,
|
prim_medium_interface: MediumInterface,
|
||||||
ray_medium: Arc<Medium>,
|
ray_medium: *const Medium,
|
||||||
) {
|
) {
|
||||||
self.material = Some(mtl);
|
self.material = mtl;
|
||||||
self.area_light = Some(area);
|
self.area_light = area;
|
||||||
if prim_medium_interface
|
|
||||||
.as_ref()
|
if prim_medium_interface.is_medium_transition() {
|
||||||
.is_some_and(|mi| mi.is_medium_transition())
|
self.common.medium_interface = *prim_medium_interface;
|
||||||
{
|
|
||||||
self.common.medium_interface = prim_medium_interface;
|
|
||||||
} else {
|
} else {
|
||||||
self.common.medium = Some(ray_medium);
|
self.common.medium = ray_medium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MediumInteraction {
|
pub struct MediumInteraction {
|
||||||
pub common: InteractionData,
|
pub common: InteractionData,
|
||||||
pub medium: Arc<Medium>,
|
pub medium: *const Medium,
|
||||||
pub phase: PhaseFunction,
|
pub phase: PhaseFunction,
|
||||||
|
pub medium_interface: MediumInterface,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MediumInteraction {
|
impl MediumInteraction {
|
||||||
|
|
@ -657,7 +664,7 @@ impl MediumInteraction {
|
||||||
p: Point3f,
|
p: Point3f,
|
||||||
wo: Vector3f,
|
wo: Vector3f,
|
||||||
time: Float,
|
time: Float,
|
||||||
medium: Arc<Medium>,
|
medium: *const Medium,
|
||||||
phase: PhaseFunction,
|
phase: PhaseFunction,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -667,10 +674,11 @@ impl MediumInteraction {
|
||||||
time,
|
time,
|
||||||
wo: wo.normalize(),
|
wo: wo.normalize(),
|
||||||
medium_interface: None,
|
medium_interface: None,
|
||||||
medium: Some(medium.clone()),
|
medium,
|
||||||
},
|
},
|
||||||
medium,
|
medium,
|
||||||
phase,
|
phase,
|
||||||
|
medium_interface: MediumInterface::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,30 @@
|
||||||
|
use crate::core::color::RGB;
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
||||||
Vector3f, VectorLike, cos_theta,
|
Vector3f, VectorLike, cos_theta,
|
||||||
};
|
};
|
||||||
use crate::core::interaction::{
|
use crate::core::interaction::{
|
||||||
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
Interaction, InteractionData, InteractionTrait, MediumInteraction, SimpleInteraction,
|
||||||
|
SurfaceInteraction,
|
||||||
};
|
};
|
||||||
use crate::core::medium::MediumInterface;
|
use crate::core::medium::MediumInterface;
|
||||||
use crate::core::pbrt::{Float, PI};
|
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||||
use crate::images::Image;
|
use crate::images::Image;
|
||||||
|
use crate::lights::*;
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum,
|
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum,
|
||||||
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
SampledSpectrum, SampledWavelengths,
|
||||||
};
|
};
|
||||||
use crate::utils::containers::InternCache;
|
use crate::utils::Transform;
|
||||||
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
||||||
use crate::utils::sampling::PiecewiseConstant2D;
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
use crate::utils::transform::TransformGeneric;
|
use crate::{Float, PI};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
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! {
|
bitflags! {
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct LightType: u32 {
|
pub struct LightType: u32 {
|
||||||
const DeltaPosition = 1;
|
const DeltaPosition = 1;
|
||||||
|
|
@ -52,15 +44,37 @@ impl LightType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct LightLeSample {
|
pub struct LightLeSample {
|
||||||
l: SampledSpectrum,
|
pub l: SampledSpectrum,
|
||||||
ray: Ray,
|
pub ray: Ray,
|
||||||
intr: Option<Interaction>,
|
pub intr: *const InteractionData,
|
||||||
pdf_pos: Float,
|
pub pdf_pos: Float,
|
||||||
pdf_dir: Float,
|
pub pdf_dir: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct LightLiSample {
|
||||||
|
pub l: SampledSpectrum,
|
||||||
|
pub wi: Vector3f,
|
||||||
|
pub pdf: Float,
|
||||||
|
pub p_light: InteractionData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
impl LightLiSample {
|
||||||
|
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: InteractionData) -> Self {
|
||||||
|
Self {
|
||||||
|
l,
|
||||||
|
wi,
|
||||||
|
pdf,
|
||||||
|
p_light,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Default, Clone)]
|
#[derive(Debug, Copy, Default, Clone)]
|
||||||
pub struct LightSampleContext {
|
pub struct LightSampleContext {
|
||||||
pub pi: Point3fi,
|
pub pi: Point3fi,
|
||||||
|
|
@ -122,38 +136,19 @@ impl From<&Interaction> for LightSampleContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct LightBaseData {
|
pub struct LightBase {
|
||||||
pub render_from_light: Transform,
|
pub render_from_light: Transform,
|
||||||
pub light_type: u32,
|
pub light_type: u32,
|
||||||
pub mi_inside: i32,
|
pub medium_interface: MediumInterface,
|
||||||
pub mi_outside: i32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl LightBase {
|
impl LightBase {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
light_type: LightType,
|
light_type: LightType,
|
||||||
render_from_light: &TransformGeneric<Float>,
|
render_from_light: &Transform,
|
||||||
medium_interface: &MediumInterface,
|
medium_interface: &MediumInterface,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -163,6 +158,14 @@ impl LightBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrum {
|
||||||
|
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
|
||||||
|
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
|
||||||
|
cache.lookup(dense_spectrum).as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LightBase {
|
||||||
fn l(
|
fn l(
|
||||||
&self,
|
&self,
|
||||||
_p: Point3f,
|
_p: Point3f,
|
||||||
|
|
@ -181,24 +184,20 @@ impl LightBase {
|
||||||
pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
SampledSpectrum::default()
|
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)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct LightBounds {
|
pub struct LightBounds {
|
||||||
bounds: Bounds3f,
|
pub bounds: Bounds3f,
|
||||||
phi: Float,
|
pub phi: Float,
|
||||||
w: Vector3f,
|
pub w: Vector3f,
|
||||||
cos_theta_o: Float,
|
pub cos_theta_o: Float,
|
||||||
cos_theta_e: Float,
|
pub cos_theta_e: Float,
|
||||||
two_sided: bool,
|
pub two_sided: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl LightBounds {
|
impl LightBounds {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bounds: &Bounds3f,
|
bounds: &Bounds3f,
|
||||||
|
|
@ -217,7 +216,9 @@ impl LightBounds {
|
||||||
two_sided,
|
two_sided,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LightBounds {
|
||||||
pub fn centroid(&self) -> Point3f {
|
pub fn centroid(&self) -> Point3f {
|
||||||
self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
|
self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
|
||||||
}
|
}
|
||||||
|
|
@ -301,9 +302,9 @@ impl LightBounds {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
pub trait LightTrait {
|
||||||
fn base(&self) -> &LightBase;
|
fn base(&self) -> &LightBase;
|
||||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
|
|
||||||
fn sample_li(
|
fn sample_li(
|
||||||
&self,
|
&self,
|
||||||
ctx: &LightSampleContext,
|
ctx: &LightSampleContext,
|
||||||
|
|
@ -311,7 +312,9 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
allow_incomplete_pdf: bool,
|
allow_incomplete_pdf: bool,
|
||||||
) -> Option<LightLiSample>;
|
) -> Option<LightLiSample>;
|
||||||
|
|
||||||
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float;
|
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float;
|
||||||
|
|
||||||
fn l(
|
fn l(
|
||||||
&self,
|
&self,
|
||||||
p: Point3f,
|
p: Point3f,
|
||||||
|
|
@ -320,16 +323,26 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
||||||
w: Vector3f,
|
w: Vector3f,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
) -> SampledSpectrum;
|
) -> SampledSpectrum;
|
||||||
|
|
||||||
fn le(&self, ray: &Ray, 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 {
|
fn light_type(&self) -> LightType {
|
||||||
self.base().light_type()
|
self.base().light_type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn bounds(&self) -> Option<LightBounds>;
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn preprocess(&mut self, scene_bounds: &Bounds3f);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[enum_dispatch(LightTrait)]
|
#[enum_dispatch(LightTrait)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum Light {
|
pub enum Light {
|
||||||
DiffuseArea(DiffuseAreaLight),
|
DiffuseArea(DiffuseAreaLight),
|
||||||
|
|
@ -342,548 +355,3 @@ pub enum Light {
|
||||||
Projection(ProjectionLight),
|
Projection(ProjectionLight),
|
||||||
Spot(SpotLight),
|
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,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use bumpalo::Bump;
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -9,18 +8,21 @@ use crate::core::bxdf::{
|
||||||
};
|
};
|
||||||
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
|
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
|
||||||
use crate::core::interaction::InteractionTrait;
|
use crate::core::interaction::InteractionTrait;
|
||||||
use crate::core::interaction::{Interaction, Shadinggeom, SurfaceInteraction};
|
use crate::core::interaction::{Interaction, ShadingGeom, SurfaceInteraction};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||||
|
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||||
use crate::core::texture::{
|
use crate::core::texture::{
|
||||||
FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator,
|
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator,
|
||||||
};
|
};
|
||||||
use crate::image::{Image, WrapMode, WrapMode2D};
|
use crate::images::{Image, WrapMode, WrapMode2D};
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::Ptr;
|
||||||
use crate::utils::hash::hash_float;
|
use crate::utils::hash::hash_float;
|
||||||
use crate::utils::math::clamp;
|
use crate::utils::math::clamp;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct MaterialEvalContext {
|
pub struct MaterialEvalContext {
|
||||||
pub texture: TextureEvalContext,
|
pub texture: TextureEvalContext,
|
||||||
pub wo: Vector3f,
|
pub wo: Vector3f,
|
||||||
|
|
@ -47,20 +49,21 @@ impl From<&SurfaceInteraction> for MaterialEvalContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct NormalBumpEvalContext {
|
pub struct NormalBumpEvalContext {
|
||||||
p: Point3f,
|
pub p: Point3f,
|
||||||
uv: Point2f,
|
pub uv: Point2f,
|
||||||
n: Normal3f,
|
pub n: Normal3f,
|
||||||
shading: Shadinggeom,
|
pub shading: ShadingGeom,
|
||||||
dpdx: Vector3f,
|
pub dpdx: Vector3f,
|
||||||
dpdy: Vector3f,
|
pub dpdy: Vector3f,
|
||||||
// All 0
|
// All 0
|
||||||
dudx: Float,
|
pub dudx: Float,
|
||||||
dudy: Float,
|
pub dudy: Float,
|
||||||
dvdx: Float,
|
pub dvdx: Float,
|
||||||
dvdy: Float,
|
pub dvdy: Float,
|
||||||
face_index: usize,
|
pub face_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&SurfaceInteraction> for NormalBumpEvalContext {
|
impl From<&SurfaceInteraction> for NormalBumpEvalContext {
|
||||||
|
|
@ -117,7 +120,7 @@ pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f,
|
||||||
|
|
||||||
pub fn bump_map<T: TextureEvaluator>(
|
pub fn bump_map<T: TextureEvaluator>(
|
||||||
tex_eval: &T,
|
tex_eval: &T,
|
||||||
displacement: &FloatTexture,
|
displacement: &GPUFloatTexture,
|
||||||
ctx: &NormalBumpEvalContext,
|
ctx: &NormalBumpEvalContext,
|
||||||
) -> (Vector3f, Vector3f) {
|
) -> (Vector3f, Vector3f) {
|
||||||
debug_assert!(tex_eval.can_evaluate(&[displacement], &[]));
|
debug_assert!(tex_eval.can_evaluate(&[displacement], &[]));
|
||||||
|
|
@ -168,12 +171,12 @@ pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
|
||||||
) -> Option<BSSRDF>;
|
) -> Option<BSSRDF>;
|
||||||
|
|
||||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
|
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
|
||||||
fn get_normal_map(&self) -> Option<Arc<Image>>;
|
fn get_normal_map(&self) -> *const Image;
|
||||||
fn get_displacement(&self) -> Option<FloatTexture>;
|
fn get_displacement(&self) -> Option<GPUFloatTexture>;
|
||||||
fn has_surface_scattering(&self) -> bool;
|
fn has_surface_scattering(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
#[enum_dispatch(MaterialTrait)]
|
#[enum_dispatch(MaterialTrait)]
|
||||||
pub enum Material {
|
pub enum Material {
|
||||||
CoatedDiffuse(CoatedDiffuseMaterial),
|
CoatedDiffuse(CoatedDiffuseMaterial),
|
||||||
|
|
@ -191,32 +194,33 @@ pub enum Material {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CoatedDiffuseMaterial {
|
pub struct CoatedDiffuseMaterial {
|
||||||
displacement: FloatTexture,
|
pub displacement: GPUFloatTexture,
|
||||||
normal_map: Option<Arc<Image>>,
|
pub normal_map: *const Image,
|
||||||
reflectance: SpectrumTexture,
|
pub reflectance: GPUSpectrumTexture,
|
||||||
albedo: SpectrumTexture,
|
pub albedo: GPUSpectrumTexture,
|
||||||
u_roughness: FloatTexture,
|
pub u_roughness: GPUFloatTexture,
|
||||||
v_roughness: FloatTexture,
|
pub v_roughness: GPUFloatTexture,
|
||||||
thickness: FloatTexture,
|
pub thickness: GPUFloatTexture,
|
||||||
g: FloatTexture,
|
pub g: GPUFloatTexture,
|
||||||
eta: Spectrum,
|
pub eta: Spectrum,
|
||||||
remap_roughness: bool,
|
pub remap_roughness: bool,
|
||||||
max_depth: usize,
|
pub max_depth: usize,
|
||||||
n_samples: usize,
|
pub n_samples: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoatedDiffuseMaterial {
|
impl CoatedDiffuseMaterial {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
reflectance: SpectrumTexture,
|
reflectance: GPUSpectrumTexture,
|
||||||
u_roughness: FloatTexture,
|
u_roughness: GPUFloatTexture,
|
||||||
v_roughness: FloatTexture,
|
v_roughness: GPUFloatTexture,
|
||||||
thickness: FloatTexture,
|
thickness: GPUFloatTexture,
|
||||||
albedo: SpectrumTexture,
|
albedo: GPUSpectrumTexture,
|
||||||
g: FloatTexture,
|
g: GPUFloatTexture,
|
||||||
eta: Spectrum,
|
eta: Spectrum,
|
||||||
displacement: FloatTexture,
|
displacement: GPUFloatTexture,
|
||||||
normal_map: Option<Arc<Image>>,
|
normal_map: *const Image,
|
||||||
remap_roughness: bool,
|
remap_roughness: bool,
|
||||||
max_depth: usize,
|
max_depth: usize,
|
||||||
n_samples: usize,
|
n_samples: usize,
|
||||||
|
|
@ -313,8 +317,8 @@ impl MaterialTrait for CoatedDiffuseMaterial {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
fn get_normal_map(&self) -> *const Image {
|
||||||
self.normal_map.clone()
|
self.normal_map
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -326,10 +330,11 @@ impl MaterialTrait for CoatedDiffuseMaterial {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct CoatedConductorMaterial {
|
pub struct CoatedConductorMaterial {
|
||||||
displacement: FloatTexture,
|
displacement: FloatTexture,
|
||||||
normal_map: Option<Arc<Image>>,
|
normal_map: *const Image,
|
||||||
interface_uroughness: FloatTexture,
|
interface_uroughness: FloatTexture,
|
||||||
interface_vroughness: FloatTexture,
|
interface_vroughness: FloatTexture,
|
||||||
thickness: FloatTexture,
|
thickness: FloatTexture,
|
||||||
|
|
@ -348,6 +353,7 @@ pub struct CoatedConductorMaterial {
|
||||||
|
|
||||||
impl CoatedConductorMaterial {
|
impl CoatedConductorMaterial {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
displacement: FloatTexture,
|
displacement: FloatTexture,
|
||||||
normal_map: Option<Arc<Image>>,
|
normal_map: Option<Arc<Image>>,
|
||||||
|
|
@ -502,8 +508,8 @@ impl MaterialTrait for CoatedConductorMaterial {
|
||||||
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
|
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
fn get_normal_map(&self) -> *const Image {
|
||||||
self.normal_map.clone()
|
self.normal_map
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -515,7 +521,8 @@ impl MaterialTrait for CoatedConductorMaterial {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ConductorMaterial;
|
pub struct ConductorMaterial;
|
||||||
impl MaterialTrait for ConductorMaterial {
|
impl MaterialTrait for ConductorMaterial {
|
||||||
fn get_bsdf<T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
|
|
@ -537,7 +544,7 @@ impl MaterialTrait for ConductorMaterial {
|
||||||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
fn get_normal_map(&self) -> *const Image {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||||
|
|
@ -547,9 +554,11 @@ impl MaterialTrait for ConductorMaterial {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct DielectricMaterial {
|
pub struct DielectricMaterial {
|
||||||
normal_map: Option<Arc<Image>>,
|
normal_map: *const Image,
|
||||||
displacement: FloatTexture,
|
displacement: FloatTexture,
|
||||||
u_roughness: FloatTexture,
|
u_roughness: FloatTexture,
|
||||||
v_roughness: FloatTexture,
|
v_roughness: FloatTexture,
|
||||||
|
|
@ -612,9 +621,11 @@ impl MaterialTrait for DielectricMaterial {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct DiffuseMaterial {
|
pub struct DiffuseMaterial {
|
||||||
normal_map: Option<Arc<Image>>,
|
normal_map: *const Image,
|
||||||
displacement: FloatTexture,
|
displacement: FloatTexture,
|
||||||
reflectance: SpectrumTexture,
|
reflectance: SpectrumTexture,
|
||||||
}
|
}
|
||||||
|
|
@ -656,8 +667,11 @@ impl MaterialTrait for DiffuseMaterial {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct DiffuseTransmissionMaterial;
|
pub struct DiffuseTransmissionMaterial;
|
||||||
|
|
||||||
impl MaterialTrait for DiffuseTransmissionMaterial {
|
impl MaterialTrait for DiffuseTransmissionMaterial {
|
||||||
fn get_bsdf<T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -692,8 +706,11 @@ impl MaterialTrait for DiffuseTransmissionMaterial {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct HairMaterial;
|
pub struct HairMaterial;
|
||||||
|
|
||||||
impl MaterialTrait for HairMaterial {
|
impl MaterialTrait for HairMaterial {
|
||||||
fn get_bsdf<T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -726,7 +743,8 @@ impl MaterialTrait for HairMaterial {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MeasuredMaterial;
|
pub struct MeasuredMaterial;
|
||||||
impl MaterialTrait for MeasuredMaterial {
|
impl MaterialTrait for MeasuredMaterial {
|
||||||
fn get_bsdf<T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
|
|
@ -759,7 +777,9 @@ impl MaterialTrait for MeasuredMaterial {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct SubsurfaceMaterial;
|
pub struct SubsurfaceMaterial;
|
||||||
impl MaterialTrait for SubsurfaceMaterial {
|
impl MaterialTrait for SubsurfaceMaterial {
|
||||||
fn get_bsdf<T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
|
|
@ -792,7 +812,9 @@ impl MaterialTrait for SubsurfaceMaterial {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ThinDielectricMaterial;
|
pub struct ThinDielectricMaterial;
|
||||||
impl MaterialTrait for ThinDielectricMaterial {
|
impl MaterialTrait for ThinDielectricMaterial {
|
||||||
fn get_bsdf<T: TextureEvaluator>(
|
fn get_bsdf<T: TextureEvaluator>(
|
||||||
|
|
@ -824,10 +846,12 @@ impl MaterialTrait for ThinDielectricMaterial {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MixMaterial {
|
pub struct MixMaterial {
|
||||||
pub amount: FloatTexture,
|
pub amount: FloatTexture,
|
||||||
pub materials: [Box<Material>; 2],
|
pub materials: [Ptr<Material>; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixMaterial {
|
impl MixMaterial {
|
||||||
|
|
@ -835,23 +859,19 @@ impl MixMaterial {
|
||||||
&self,
|
&self,
|
||||||
tex_eval: &T,
|
tex_eval: &T,
|
||||||
ctx: &MaterialEvalContext,
|
ctx: &MaterialEvalContext,
|
||||||
) -> &Material {
|
) -> Option<&Material> {
|
||||||
let amt = tex_eval.evaluate_float(&self.amount, ctx);
|
let amt = tex_eval.evaluate_float(&self.amount, ctx);
|
||||||
|
|
||||||
if amt <= 0.0 {
|
let index = if amt <= 0.0 {
|
||||||
return &self.materials[0];
|
0
|
||||||
}
|
} else if amt >= 1.0 {
|
||||||
if amt >= 1.0 {
|
1
|
||||||
return &self.materials[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
let u = hash_float(&(ctx.p, ctx.wo));
|
|
||||||
|
|
||||||
if amt < u {
|
|
||||||
&self.materials[0]
|
|
||||||
} else {
|
} else {
|
||||||
&self.materials[1]
|
let u = hash_float(&(ctx.p, ctx.wo));
|
||||||
}
|
if amt < u { 0 } else { 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
self.materials[index].get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -862,8 +882,11 @@ impl MaterialTrait for MixMaterial {
|
||||||
ctx: &MaterialEvalContext,
|
ctx: &MaterialEvalContext,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
) -> BSDF {
|
) -> BSDF {
|
||||||
let chosen_mat = self.choose_material(tex_eval, ctx);
|
if let Some(mat) = self.choose_material(tex_eval, ctx) {
|
||||||
chosen_mat.get_bsdf(tex_eval, ctx, lambda)
|
mat.get_bsdf(tex_eval, ctx, lambda)
|
||||||
|
} else {
|
||||||
|
BSDF::empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bssrdf<T>(
|
fn get_bssrdf<T>(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use bumpalo::Bump;
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
@ -8,13 +7,14 @@ use crate::core::geometry::{
|
||||||
use crate::core::pbrt::{Float, INV_4_PI, PI};
|
use crate::core::pbrt::{Float, INV_4_PI, PI};
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
|
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
|
||||||
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
|
||||||
};
|
};
|
||||||
use crate::utils::containers::SampledGrid;
|
use crate::utils::containers::SampledGrid;
|
||||||
use crate::utils::math::{clamp, square};
|
use crate::utils::math::{clamp, square};
|
||||||
use crate::utils::rng::Rng;
|
use crate::utils::rng::Rng;
|
||||||
use crate::utils::transform::TransformGeneric;
|
use crate::utils::transform::TransformGeneric;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PhaseFunctionSample {
|
pub struct PhaseFunctionSample {
|
||||||
pub p: Float,
|
pub p: Float,
|
||||||
|
|
@ -35,12 +35,14 @@ pub trait PhaseFunctionTrait {
|
||||||
fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float;
|
fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[enum_dispatch(PhaseFunctionTrait)]
|
#[enum_dispatch(PhaseFunctionTrait)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PhaseFunction {
|
pub enum PhaseFunction {
|
||||||
HenyeyGreenstein(HGPhaseFunction),
|
HenyeyGreenstein(HGPhaseFunction),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct HGPhaseFunction {
|
pub struct HGPhaseFunction {
|
||||||
g: Float,
|
g: Float,
|
||||||
|
|
@ -85,14 +87,19 @@ impl PhaseFunctionTrait for HGPhaseFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MajorantGrid {
|
pub struct MajorantGrid {
|
||||||
pub bounds: Bounds3f,
|
pub bounds: Bounds3f,
|
||||||
pub res: Point3i,
|
pub res: Point3i,
|
||||||
pub voxels: Vec<Float>,
|
pub voxels: *const Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for MajorantGrid {}
|
||||||
|
unsafe impl Sync for MajorantGrid {}
|
||||||
|
|
||||||
impl MajorantGrid {
|
impl MajorantGrid {
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
|
pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bounds,
|
bounds,
|
||||||
|
|
@ -100,18 +107,37 @@ impl MajorantGrid {
|
||||||
voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize),
|
voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
!self.voxels.is_null()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn lookup(&self, x: i32, y: i32, z: i32) -> Float {
|
pub fn lookup(&self, x: i32, y: i32, z: i32) -> Float {
|
||||||
|
if !self.is_valid() {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
|
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
|
||||||
|
|
||||||
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
||||||
self.voxels[idx as usize]
|
unsafe { *self.voxels.add(idx as usize) }
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set(&mut self, x: i32, y: i32, z: i32, v: Float) {
|
|
||||||
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
|
#[inline(always)]
|
||||||
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
pub fn set(&self, x: i32, y: i32, z: i32, v: Float) {
|
||||||
self.voxels[idx as usize] = v;
|
if !self.is_valid() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = x + self.res.x() * (y + self.res.y() * z);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
*self.voxels.add(idx as usize) = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,6 +156,7 @@ impl MajorantGrid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct RayMajorantSegment {
|
pub struct RayMajorantSegment {
|
||||||
t_min: Float,
|
t_min: Float,
|
||||||
|
|
@ -137,6 +164,8 @@ pub struct RayMajorantSegment {
|
||||||
sigma_maj: SampledSpectrum,
|
sigma_maj: SampledSpectrum,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum RayMajorantIterator {
|
pub enum RayMajorantIterator {
|
||||||
Homogeneous(HomogeneousMajorantIterator),
|
Homogeneous(HomogeneousMajorantIterator),
|
||||||
DDA(DDAMajorantIterator),
|
DDA(DDAMajorantIterator),
|
||||||
|
|
@ -155,6 +184,9 @@ impl Iterator for RayMajorantIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct HomogeneousMajorantIterator {
|
pub struct HomogeneousMajorantIterator {
|
||||||
called: bool,
|
called: bool,
|
||||||
seg: RayMajorantSegment,
|
seg: RayMajorantSegment,
|
||||||
|
|
@ -186,6 +218,8 @@ impl Iterator for HomogeneousMajorantIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct DDAMajorantIterator {
|
pub struct DDAMajorantIterator {
|
||||||
sigma_t: SampledSpectrum,
|
sigma_t: SampledSpectrum,
|
||||||
t_min: Float,
|
t_min: Float,
|
||||||
|
|
@ -197,6 +231,7 @@ pub struct DDAMajorantIterator {
|
||||||
voxel_limit: [i32; 3],
|
voxel_limit: [i32; 3],
|
||||||
voxel: [i32; 3],
|
voxel: [i32; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DDAMajorantIterator {
|
impl DDAMajorantIterator {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
|
|
@ -209,7 +244,7 @@ impl DDAMajorantIterator {
|
||||||
t_min,
|
t_min,
|
||||||
t_max,
|
t_max,
|
||||||
sigma_t: *sigma_t,
|
sigma_t: *sigma_t,
|
||||||
grid: grid.clone(),
|
grid: *grid,
|
||||||
next_crossing_t: [0.0; 3],
|
next_crossing_t: [0.0; 3],
|
||||||
delta_t: [0.0; 3],
|
delta_t: [0.0; 3],
|
||||||
step: [0; 3],
|
step: [0; 3],
|
||||||
|
|
@ -227,28 +262,29 @@ impl DDAMajorantIterator {
|
||||||
|
|
||||||
let p_grid_start = grid.bounds.offset(&ray.at(t_min));
|
let p_grid_start = grid.bounds.offset(&ray.at(t_min));
|
||||||
let grid_intersect = Vector3f::from(p_grid_start);
|
let grid_intersect = Vector3f::from(p_grid_start);
|
||||||
|
let res = [grid.res.x, grid.res.y, grid.res.z];
|
||||||
|
|
||||||
for axis in 0..3 {
|
for axis in 0..3 {
|
||||||
iter.voxel[axis] = clamp(
|
iter.voxel[axis] = clamp(
|
||||||
(grid_intersect[axis] * grid.res[axis] as Float) as i32,
|
(grid_intersect[axis] * res[axis] as Float) as i32,
|
||||||
0,
|
0,
|
||||||
grid.res[axis] - 1,
|
res[axis] - 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
iter.delta_t[axis] = 1.0 / (ray_grid_d[axis].abs() * grid.res[axis] as Float);
|
iter.delta_t[axis] = 1.0 / (ray_grid_d[axis].abs() * res[axis] as Float);
|
||||||
|
|
||||||
if ray_grid_d[axis] == -0.0 {
|
if ray_grid_d[axis] == -0.0 {
|
||||||
ray_grid_d[axis] = 0.0;
|
ray_grid_d[axis] = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ray_grid_d[axis] >= 0.0 {
|
if ray_grid_d[axis] >= 0.0 {
|
||||||
let next_voxel_pos = (iter.voxel[axis] + 1) as Float / grid.res[axis] as Float;
|
let next_voxel_pos = (iter.voxel[axis] + 1) as Float / res[axis] as Float;
|
||||||
iter.next_crossing_t[axis] =
|
iter.next_crossing_t[axis] =
|
||||||
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
||||||
iter.step[axis] = 1;
|
iter.step[axis] = 1;
|
||||||
iter.voxel_limit[axis] = grid.res[axis];
|
iter.voxel_limit[axis] = res[axis];
|
||||||
} else {
|
} else {
|
||||||
let next_voxel_pos = (iter.voxel[axis]) as Float / grid.res[axis] as Float;
|
let next_voxel_pos = (iter.voxel[axis]) as Float / res[axis] as Float;
|
||||||
iter.next_crossing_t[axis] =
|
iter.next_crossing_t[axis] =
|
||||||
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
||||||
iter.step[axis] = -1;
|
iter.step[axis] = -1;
|
||||||
|
|
@ -313,6 +349,8 @@ impl Iterator for DDAMajorantIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct MediumProperties {
|
pub struct MediumProperties {
|
||||||
pub sigma_a: SampledSpectrum,
|
pub sigma_a: SampledSpectrum,
|
||||||
pub sigma_s: SampledSpectrum,
|
pub sigma_s: SampledSpectrum,
|
||||||
|
|
@ -335,7 +373,6 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
t_max: Float,
|
t_max: Float,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
buf: &Bump,
|
|
||||||
) -> RayMajorantIterator;
|
) -> RayMajorantIterator;
|
||||||
|
|
||||||
fn sample_t_maj<F>(
|
fn sample_t_maj<F>(
|
||||||
|
|
@ -354,8 +391,7 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
|
||||||
t_max *= len;
|
t_max *= len;
|
||||||
ray.d /= len;
|
ray.d /= len;
|
||||||
|
|
||||||
let buf = Bump::new();
|
let mut iter = self.sample_ray(&ray, t_max, lambda);
|
||||||
let mut iter = self.sample_ray(&ray, t_max, lambda, &buf);
|
|
||||||
let mut t_maj = SampledSpectrum::new(1.0);
|
let mut t_maj = SampledSpectrum::new(1.0);
|
||||||
|
|
||||||
while let Some(seg) = iter.next() {
|
while let Some(seg) = iter.next() {
|
||||||
|
|
@ -463,7 +499,6 @@ impl MediumTrait for HomogeneousMedium {
|
||||||
_ray: &Ray,
|
_ray: &Ray,
|
||||||
t_max: Float,
|
t_max: Float,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
_scratch: &Bump,
|
|
||||||
) -> RayMajorantIterator {
|
) -> RayMajorantIterator {
|
||||||
let sigma_a = self.sigma_a_spec.sample(lambda);
|
let sigma_a = self.sigma_a_spec.sample(lambda);
|
||||||
let sigma_s = self.sigma_s_spec.sample(lambda);
|
let sigma_s = self.sigma_s_spec.sample(lambda);
|
||||||
|
|
@ -475,7 +510,8 @@ impl MediumTrait for HomogeneousMedium {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct GridMedium {
|
pub struct GridMedium {
|
||||||
bounds: Bounds3f,
|
bounds: Bounds3f,
|
||||||
render_from_medium: TransformGeneric<Float>,
|
render_from_medium: TransformGeneric<Float>,
|
||||||
|
|
@ -483,7 +519,7 @@ pub struct GridMedium {
|
||||||
sigma_s_spec: DenselySampledSpectrum,
|
sigma_s_spec: DenselySampledSpectrum,
|
||||||
density_grid: SampledGrid<Float>,
|
density_grid: SampledGrid<Float>,
|
||||||
phase: HGPhaseFunction,
|
phase: HGPhaseFunction,
|
||||||
temperature_grid: Option<SampledGrid<Float>>,
|
temperature_grid: SampledGrid<Float>,
|
||||||
le_spec: DenselySampledSpectrum,
|
le_spec: DenselySampledSpectrum,
|
||||||
le_scale: SampledGrid<Float>,
|
le_scale: SampledGrid<Float>,
|
||||||
is_emissive: bool,
|
is_emissive: bool,
|
||||||
|
|
@ -492,15 +528,16 @@ pub struct GridMedium {
|
||||||
|
|
||||||
impl GridMedium {
|
impl GridMedium {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bounds: &Bounds3f,
|
bounds: &Bounds3f,
|
||||||
render_from_medium: &TransformGeneric<Float>,
|
render_from_medium: &Transform,
|
||||||
sigma_a: &Spectrum,
|
sigma_a: &Spectrum,
|
||||||
sigma_s: &Spectrum,
|
sigma_s: &Spectrum,
|
||||||
sigma_scale: Float,
|
sigma_scale: Float,
|
||||||
g: Float,
|
g: Float,
|
||||||
density_grid: SampledGrid<Float>,
|
density_grid: SampledGrid<Float>,
|
||||||
temperature_grid: Option<SampledGrid<Float>>,
|
temperature_grid: SampledGrid<Float>,
|
||||||
le: &Spectrum,
|
le: &Spectrum,
|
||||||
le_scale: SampledGrid<Float>,
|
le_scale: SampledGrid<Float>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -588,7 +625,6 @@ impl MediumTrait for GridMedium {
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
t_max: Float,
|
t_max: Float,
|
||||||
lambda: &SampledWavelengths,
|
lambda: &SampledWavelengths,
|
||||||
_buf: &Bump,
|
|
||||||
) -> RayMajorantIterator {
|
) -> RayMajorantIterator {
|
||||||
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
|
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
|
||||||
|
|
||||||
|
|
@ -621,29 +657,31 @@ impl MediumTrait for GridMedium {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RGBGridMedium {
|
pub struct RGBGridMedium {
|
||||||
bounds: Bounds3f,
|
bounds: Bounds3f,
|
||||||
render_from_medium: TransformGeneric<Float>,
|
render_from_medium: Transform,
|
||||||
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
|
||||||
le_scale: Float,
|
|
||||||
phase: HGPhaseFunction,
|
phase: HGPhaseFunction,
|
||||||
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
le_scale: Float,
|
||||||
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
|
||||||
sigma_scale: Float,
|
sigma_scale: Float,
|
||||||
|
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||||
|
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||||
|
le_grid: SampledGrid<RGBIlluminantSpectrum>,
|
||||||
majorant_grid: MajorantGrid,
|
majorant_grid: MajorantGrid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBGridMedium {
|
impl RGBGridMedium {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
bounds: &Bounds3f,
|
bounds: &Bounds3f,
|
||||||
render_from_medium: &TransformGeneric<Float>,
|
render_from_medium: &TransformGeneric<Float>,
|
||||||
g: Float,
|
g: Float,
|
||||||
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||||
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||||
sigma_scale: Float,
|
sigma_scale: Float,
|
||||||
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
le_grid: SampledGrid<RGBIlluminantSpectrum>,
|
||||||
le_scale: Float,
|
le_scale: Float,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
|
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
|
||||||
|
|
@ -679,31 +717,29 @@ impl RGBGridMedium {
|
||||||
|
|
||||||
impl MediumTrait for RGBGridMedium {
|
impl MediumTrait for RGBGridMedium {
|
||||||
fn is_emissive(&self) -> bool {
|
fn is_emissive(&self) -> bool {
|
||||||
self.le_grid.is_some() && self.le_scale > 0.
|
self.le_grid.is_valid() && self.le_scale > 0.
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties {
|
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties {
|
||||||
let p_transform = self.render_from_medium.apply_inverse(p);
|
let p_transform = self.render_from_medium.apply_inverse(p);
|
||||||
let p = Point3f::from(self.bounds.offset(&p_transform));
|
let p = Point3f::from(self.bounds.offset(&p_transform));
|
||||||
let convert = |s: &RGBUnboundedSpectrum| s.sample(lambda);
|
let convert = |s: &RGBUnboundedSpectrum| s.sample(lambda);
|
||||||
let sigma_a = self.sigma_scale
|
let sigma_a = if self.sigma_a_grid.is_valid() {
|
||||||
* self
|
self.sigma_scale * self.sigma_a_grid.lookup_convert(p, convert)
|
||||||
.sigma_a_grid
|
} else {
|
||||||
.as_ref()
|
SampledSpectrum::new(1.0) * self.sigma_scale // Or 0.0? Check PBRT logic
|
||||||
.map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert));
|
};
|
||||||
|
let sigma_s = if self.sigma_s_grid.is_valid() {
|
||||||
|
self.sigma_scale * self.sigma_s_grid.lookup_convert(p, convert)
|
||||||
|
} else {
|
||||||
|
SampledSpectrum::new(1.0) * self.sigma_scale
|
||||||
|
};
|
||||||
|
|
||||||
let sigma_s = self.sigma_scale
|
let le = if self.le_grid.is_valid() && self.le_scale > 0.0 {
|
||||||
* self
|
self.le_grid.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale
|
||||||
.sigma_s_grid
|
} else {
|
||||||
.as_ref()
|
SampledSpectrum::new(0.0)
|
||||||
.map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert));
|
};
|
||||||
|
|
||||||
let le = self
|
|
||||||
.le_grid
|
|
||||||
.as_ref()
|
|
||||||
.filter(|_| self.le_scale > 0.0)
|
|
||||||
.map(|g| g.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale)
|
|
||||||
.unwrap_or_else(|| SampledSpectrum::new(0.0));
|
|
||||||
|
|
||||||
MediumProperties {
|
MediumProperties {
|
||||||
sigma_a,
|
sigma_a,
|
||||||
|
|
@ -718,7 +754,6 @@ impl MediumTrait for RGBGridMedium {
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
t_max: Float,
|
t_max: Float,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_buf: &Bump,
|
|
||||||
) -> RayMajorantIterator {
|
) -> RayMajorantIterator {
|
||||||
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
|
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
|
||||||
|
|
||||||
|
|
@ -732,6 +767,7 @@ impl MediumTrait for RGBGridMedium {
|
||||||
if local_ray.d.y() < 0.0 { 1 } else { 0 },
|
if local_ray.d.y() < 0.0 { 1 } else { 0 },
|
||||||
if local_ray.d.z() < 0.0 { 1 } else { 0 },
|
if local_ray.d.z() < 0.0 { 1 } else { 0 },
|
||||||
];
|
];
|
||||||
|
|
||||||
let (t_min, t_max) =
|
let (t_min, t_max) =
|
||||||
match self
|
match self
|
||||||
.bounds
|
.bounds
|
||||||
|
|
@ -763,7 +799,6 @@ impl MediumTrait for CloudMedium {
|
||||||
_ray: &Ray,
|
_ray: &Ray,
|
||||||
_t_max: Float,
|
_t_max: Float,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_buf: &Bump,
|
|
||||||
) -> RayMajorantIterator {
|
) -> RayMajorantIterator {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
@ -782,28 +817,43 @@ impl MediumTrait for NanoVDBMedium {
|
||||||
_ray: &Ray,
|
_ray: &Ray,
|
||||||
_t_max: Float,
|
_t_max: Float,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_buf: &Bump,
|
|
||||||
) -> RayMajorantIterator {
|
) -> RayMajorantIterator {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct MediumInterface {
|
pub struct MediumInterface {
|
||||||
pub inside: Option<Arc<Medium>>,
|
pub inside: *const Medium,
|
||||||
pub outside: Option<Arc<Medium>>,
|
pub outside: *const Medium,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MediumInterface {
|
unsafe impl Send for MediumInterface {}
|
||||||
pub fn new(inside: Option<Arc<Medium>>, outside: Option<Arc<Medium>>) -> Self {
|
unsafe impl Sync for MediumInterface {}
|
||||||
Self { inside, outside }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_medium_transition(&self) -> bool {
|
impl Default for MediumInterface {
|
||||||
match (&self.inside, &self.outside) {
|
fn default() -> Self {
|
||||||
(Some(inside), Some(outside)) => !Arc::ptr_eq(inside, outside),
|
Self {
|
||||||
(None, None) => false,
|
inside: core::ptr::null(),
|
||||||
_ => true,
|
outside: core::ptr::null(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MediumInterface {
|
||||||
|
pub fn new(inside: *const Medium, outside: *const Medium) -> Self {
|
||||||
|
Self { inside, outside }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
inside: core::ptr::null(),
|
||||||
|
outside: core::ptr::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_medium_transition(&self) -> bool {
|
||||||
|
self.inside != self.outside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ pub mod aggregates;
|
||||||
pub mod bssrdf;
|
pub mod bssrdf;
|
||||||
pub mod bxdf;
|
pub mod bxdf;
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
|
pub mod color;
|
||||||
pub mod film;
|
pub mod film;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::core::aggregates::LinearBVHNode;
|
use crate::core::aggregates::LinearBVHNode;
|
||||||
use crate::core::geometry::{Bounds3f, Ray};
|
use crate::core::geometry::{Bounds3f, Ray};
|
||||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||||
|
use crate::core::light::Light;
|
||||||
use crate::core::material::Material;
|
use crate::core::material::Material;
|
||||||
use crate::core::medium::{Medium, MediumInterface};
|
use crate::core::medium::{Medium, MediumInterface};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::texture::{FloatTextureTrait, TextureEvalContext};
|
use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
|
||||||
use crate::lights::Light;
|
|
||||||
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
|
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
|
||||||
use crate::utils::hash::hash_float;
|
use crate::utils::hash::hash_float;
|
||||||
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
|
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
|
||||||
|
|
@ -14,21 +14,25 @@ use enum_dispatch::enum_dispatch;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait PrimitiveTrait: Send + Sync + std::fmt::Debug {
|
pub trait PrimitiveTrait {
|
||||||
fn bounds(&self) -> Bounds3f;
|
fn bounds(&self) -> Bounds3f;
|
||||||
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
|
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
|
||||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool;
|
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct GeometricPrimitive {
|
pub struct GeometricPrimitive {
|
||||||
shape: Arc<Shape>,
|
shape: *const Shape,
|
||||||
material: Arc<Material>,
|
material: *const Material,
|
||||||
area_light: Arc<Light>,
|
area_light: *const Light,
|
||||||
medium_interface: MediumInterface,
|
medium_interface: MediumInterface,
|
||||||
alpha: Option<Arc<dyn FloatTextureTrait>>,
|
alpha: *const GPUFloatTexture,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for GeometricPrimitive {}
|
||||||
|
unsafe impl Sync for GeometricPrimitive {}
|
||||||
|
|
||||||
impl PrimitiveTrait for GeometricPrimitive {
|
impl PrimitiveTrait for GeometricPrimitive {
|
||||||
fn bounds(&self) -> Bounds3f {
|
fn bounds(&self) -> Bounds3f {
|
||||||
self.shape.bounds()
|
self.shape.bounds()
|
||||||
|
|
@ -80,8 +84,9 @@ impl PrimitiveTrait for GeometricPrimitive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
pub struct SimplePrimitiv {
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct SimplePrimitive {
|
||||||
shape: Arc<Shape>,
|
shape: Arc<Shape>,
|
||||||
material: Arc<Material>,
|
material: Arc<Material>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
use crate::Float;
|
||||||
|
use crate::core::color::{RGB, XYZ};
|
||||||
|
use crate::spectra::*;
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
|
#[enum_dispatch]
|
||||||
|
pub trait SpectrumTrait: Copy {
|
||||||
|
fn evaluate(&self, lambda: Float) -> Float;
|
||||||
|
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
SampledSpectrum::from_fn(|i| self.evaluate(lambda[i]))
|
||||||
|
}
|
||||||
|
fn max_value(&self) -> Float;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct StandardSpectra {
|
||||||
|
pub x: DenselySampledSpectrum,
|
||||||
|
pub y: DenselySampledSpectrum,
|
||||||
|
pub z: DenselySampledSpectrum,
|
||||||
|
pub d65: DenselySampledSpectrum,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for StandardSpectra {}
|
||||||
|
unsafe impl Sync for StandardSpectra {}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[enum_dispatch(SpectrumTrait)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Spectrum {
|
||||||
|
Constant(ConstantSpectrum),
|
||||||
|
Dense(DenselySampledSpectrum),
|
||||||
|
Piecewise(PiecewiseLinearSpectrum),
|
||||||
|
Blackbody(BlackbodySpectrum),
|
||||||
|
RGBAlbedo(RGBAlbedoSpectrum),
|
||||||
|
RGBIlluminant(RGBIlluminantSpectrum),
|
||||||
|
RGBUnbounded(RGBUnboundedSpectrum),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spectrum {
|
||||||
|
pub fn std_illuminant_d65() -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ {
|
||||||
|
let x = self.inner_product(&std.x());
|
||||||
|
let y = self.inner_product(&std.y());
|
||||||
|
let z = self.inner_product(&std.z());
|
||||||
|
|
||||||
|
XYZ::new(x, y, z) / CIE_Y_INTEGRAL
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_rgb(&self, cs: &RGBColorSpace, std: &StandardSpectra) -> RGB {
|
||||||
|
let xyz = self.to_xyz(std);
|
||||||
|
cs.to_rgb(xyz)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_product(&self, other: &Spectrum) -> Float {
|
||||||
|
let mut integral = 0.0;
|
||||||
|
for lambda in LAMBDA_MIN..=LAMBDA_MAX {
|
||||||
|
let l = lambda as Float;
|
||||||
|
integral += self.evaluate(l) * other.evaluate(l);
|
||||||
|
}
|
||||||
|
integral
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_constant(&self) -> bool {
|
||||||
|
matches!(self, Spectrum::Constant(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
|
use crate::core::color::ColorEncoding;
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
|
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
|
||||||
};
|
};
|
||||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::core::pbrt::{INV_2_PI, INV_PI, PI};
|
|
||||||
use crate::images::WrapMode;
|
use crate::images::WrapMode;
|
||||||
use crate::spectra::color::ColorEncoding;
|
|
||||||
use crate::spectra::{
|
use crate::spectra::{
|
||||||
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||||
SampledWavelengths, Spectrum, SpectrumTrait,
|
SampledWavelengths,
|
||||||
};
|
};
|
||||||
use crate::textures::*;
|
use crate::textures::*;
|
||||||
use crate::utils::Transform;
|
use crate::utils::Transform;
|
||||||
use crate::utils::math::square;
|
use crate::utils::math::square;
|
||||||
|
use crate::{Float, INV_2_PI, INV_PI, PI};
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct TexCoord2D {
|
pub struct TexCoord2D {
|
||||||
pub st: Point2f,
|
pub st: Point2f,
|
||||||
pub dsdx: Float,
|
pub dsdx: Float,
|
||||||
|
|
@ -24,7 +25,7 @@ pub struct TexCoord2D {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub enum TextureMapping2D {
|
pub enum TextureMapping2D {
|
||||||
UV(UVMapping),
|
UV(UVMapping),
|
||||||
Spherical(SphericalMapping),
|
Spherical(SphericalMapping),
|
||||||
|
|
@ -43,12 +44,13 @@ impl TextureMapping2D {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct UVMapping {
|
pub struct UVMapping {
|
||||||
su: Float,
|
pub su: Float,
|
||||||
sv: Float,
|
pub sv: Float,
|
||||||
du: Float,
|
pub du: Float,
|
||||||
dv: Float,
|
pub dv: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UVMapping {
|
impl Default for UVMapping {
|
||||||
|
|
@ -83,7 +85,8 @@ impl UVMapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct SphericalMapping {
|
pub struct SphericalMapping {
|
||||||
texture_from_render: Transform,
|
texture_from_render: Transform,
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +127,8 @@ impl SphericalMapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct CylindricalMapping {
|
pub struct CylindricalMapping {
|
||||||
texture_from_render: Transform,
|
texture_from_render: Transform,
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +162,8 @@ impl CylindricalMapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct PlanarMapping {
|
pub struct PlanarMapping {
|
||||||
texture_from_render: Transform,
|
texture_from_render: Transform,
|
||||||
vs: Vector3f,
|
vs: Vector3f,
|
||||||
|
|
@ -203,6 +208,8 @@ impl PlanarMapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct TexCoord3D {
|
pub struct TexCoord3D {
|
||||||
pub p: Point3f,
|
pub p: Point3f,
|
||||||
pub dpdx: Vector3f,
|
pub dpdx: Vector3f,
|
||||||
|
|
@ -213,7 +220,8 @@ pub trait TextureMapping3DTrait {
|
||||||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D;
|
fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub enum TextureMapping3D {
|
pub enum TextureMapping3D {
|
||||||
PointTransform(PointTransformMapping),
|
PointTransform(PointTransformMapping),
|
||||||
}
|
}
|
||||||
|
|
@ -226,15 +234,17 @@ impl TextureMapping3D {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Copy)]
|
||||||
pub struct PointTransformMapping {
|
pub struct PointTransformMapping {
|
||||||
texture_from_render: Transform,
|
pub texture_from_render: Transform,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointTransformMapping {
|
impl PointTransformMapping {
|
||||||
pub fn new(texture_from_render: &Transform) -> Self {
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
pub fn new(texture_from_render: Transform) -> Self {
|
||||||
Self {
|
Self {
|
||||||
texture_from_render: *texture_from_render,
|
texture_from_render,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,7 +257,8 @@ impl PointTransformMapping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct TextureEvalContext {
|
pub struct TextureEvalContext {
|
||||||
pub p: Point3f,
|
pub p: Point3f,
|
||||||
pub dpdx: Vector3f,
|
pub dpdx: Vector3f,
|
||||||
|
|
@ -325,10 +336,11 @@ impl From<&Interaction> for TextureEvalContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum GPUFloatTexture {
|
pub enum GPUFloatTexture {
|
||||||
Constant(FloatConstantTexture),
|
Constant(FloatConstantTexture),
|
||||||
DirectionMix(FloatDirectionMixTexture),
|
DirectionMix(GPUFloatDirectionMixTexture),
|
||||||
Scaled(FloatScaledTexture),
|
Scaled(GPUFloatScaledTexture),
|
||||||
Bilerp(FloatBilerpTexture),
|
Bilerp(FloatBilerpTexture),
|
||||||
Checkerboard(FloatCheckerboardTexture),
|
Checkerboard(FloatCheckerboardTexture),
|
||||||
Dots(FloatDotsTexture),
|
Dots(FloatDotsTexture),
|
||||||
|
|
@ -359,6 +371,7 @@ impl GPUFloatTexture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum SpectrumType {
|
pub enum SpectrumType {
|
||||||
Illuminant,
|
Illuminant,
|
||||||
|
|
@ -367,14 +380,16 @@ pub enum SpectrumType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[enum_dispatch]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum GPUSpectrumTexture {
|
pub enum GPUSpectrumTexture {
|
||||||
Constant(SpectrumConstantTexture),
|
Constant(SpectrumConstantTexture),
|
||||||
Bilerp(SpectrumBilerpTexture),
|
Bilerp(SpectrumBilerpTexture),
|
||||||
Checkerboard(SpectrumCheckerboardTexture),
|
Checkerboard(SpectrumCheckerboardTexture),
|
||||||
Marble(MarbleTexture),
|
Marble(MarbleTexture),
|
||||||
DirectionMix(SpectrumDirectionMixTexture),
|
DirectionMix(GPUSpectrumDirectionMixTexture),
|
||||||
Dots(SpectrumDotsTexture),
|
Dots(SpectrumDotsTexture),
|
||||||
Scaled(SpectrumScaledTexture),
|
Scaled(GPUSpectrumScaledTexture),
|
||||||
Image(GPUSpectrumImageTexture),
|
Image(GPUSpectrumImageTexture),
|
||||||
Ptex(GPUSpectrumPtexTexture),
|
Ptex(GPUSpectrumPtexTexture),
|
||||||
Mix(GPUSpectrumMixTexture),
|
Mix(GPUSpectrumMixTexture),
|
||||||
|
|
@ -396,3 +411,42 @@ impl GPUSpectrumTexture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TextureEvaluator: Send + Sync {
|
||||||
|
fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float;
|
||||||
|
fn evaluate_spectrum(
|
||||||
|
&self,
|
||||||
|
tex: &GPUSpectrumTexture,
|
||||||
|
ctx: &TextureEvalContext,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> SampledSpectrum;
|
||||||
|
|
||||||
|
fn can_evaluate(&self, _ftex: &[&GPUFloatTexture], _stex: &[&GPUSpectrumTexture]) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Default)]
|
||||||
|
pub struct UniversalTextureEvaluator;
|
||||||
|
|
||||||
|
impl TextureEvaluator for UniversalTextureEvaluator {
|
||||||
|
fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float {
|
||||||
|
tex.evaluate(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_spectrum(
|
||||||
|
&self,
|
||||||
|
tex: &GPUSpectrumTexture,
|
||||||
|
ctx: &TextureEvalContext,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> SampledSpectrum {
|
||||||
|
tex.evaluate(ctx, lambda)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_evaluate(
|
||||||
|
&self,
|
||||||
|
_float_textures: &[&GPUFloatTexture],
|
||||||
|
_spectrum_textures: &[&GPUSpectrumTexture],
|
||||||
|
) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,9 @@ pub mod gaussian;
|
||||||
pub mod lanczos;
|
pub mod lanczos;
|
||||||
pub mod mitchell;
|
pub mod mitchell;
|
||||||
pub mod triangle;
|
pub mod triangle;
|
||||||
|
|
||||||
|
pub use boxf::*;
|
||||||
|
pub use gaussian::*;
|
||||||
|
pub use lanczos::*;
|
||||||
|
pub use mitchell::*;
|
||||||
|
pub use triangle::*;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ mod pipeline;
|
||||||
|
|
||||||
use pipeline::*;
|
use pipeline::*;
|
||||||
|
|
||||||
use crate::camera::{Camera, CameraTrait};
|
|
||||||
use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
|
use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
|
||||||
use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
|
use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
|
||||||
use crate::core::film::{FilmTrait, VisibleSurface};
|
use crate::core::camera::Camera;
|
||||||
|
use crate::core::film::VisibleSurface;
|
||||||
use crate::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike};
|
use crate::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike};
|
||||||
use crate::core::interaction::{
|
use crate::core::interaction::{
|
||||||
self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
||||||
|
|
@ -15,8 +15,8 @@ use crate::core::options::get_options;
|
||||||
use crate::core::pbrt::{Float, SHADOW_EPSILON};
|
use crate::core::pbrt::{Float, SHADOW_EPSILON};
|
||||||
use crate::core::primitive::{Primitive, PrimitiveTrait};
|
use crate::core::primitive::{Primitive, PrimitiveTrait};
|
||||||
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
|
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
|
||||||
|
use crate::lights::sampler::LightSamplerTrait;
|
||||||
use crate::lights::sampler::{LightSampler, UniformLightSampler};
|
use crate::lights::sampler::{LightSampler, UniformLightSampler};
|
||||||
use crate::lights::{Light, LightSampleContext, LightTrait, sampler::LightSamplerTrait};
|
|
||||||
use crate::shapes::ShapeIntersection;
|
use crate::shapes::ShapeIntersection;
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::hash::{hash_buffer, mix_bits};
|
use crate::utils::hash::{hash_buffer, mix_bits};
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,259 @@
|
||||||
|
use crate::PI;
|
||||||
|
use crate::core::color::{RGB, XYZ};
|
||||||
use crate::core::geometry::*;
|
use crate::core::geometry::*;
|
||||||
|
use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction};
|
||||||
|
use crate::core::light::{
|
||||||
|
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
|
||||||
|
};
|
||||||
|
use crate::core::medium::MediumInterface;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
// use crate::core::texture::GpuFloatTextureHandle;
|
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||||
// use crate::shapes::GpuShapeHandle;
|
use crate::core::texture::{GPUFloatTexture, TextureEvalContext, UniversalTextureEvaluator};
|
||||||
|
use crate::images::Image;
|
||||||
|
use crate::shapes::{Shape, ShapeSampleContext};
|
||||||
use crate::spectra::*;
|
use crate::spectra::*;
|
||||||
|
use crate::utils::Transform;
|
||||||
|
use crate::utils::hash::hash_float;
|
||||||
|
|
||||||
#[repr(C)]
|
#[derive(Clone, Debug)]
|
||||||
#[derive(Clone, Copy, Default)]
|
|
||||||
pub struct DiffuseAreaLight {
|
pub struct DiffuseAreaLight {
|
||||||
pub lemit_coeffs: [Float; 32],
|
pub base: LightBase,
|
||||||
pub scale: Float,
|
pub shape: *const Shape,
|
||||||
|
pub alpha: *const GPUFloatTexture,
|
||||||
pub area: Float,
|
pub area: Float,
|
||||||
pub two_sided: bool,
|
pub two_sided: bool,
|
||||||
pub image_id: i32,
|
pub lemit: DenselySampledSpectrum,
|
||||||
pub shape_id: u32,
|
pub scale: Float,
|
||||||
|
pub image: *const Image,
|
||||||
|
pub image_color_space: RGBColorSpace,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LightTrait for DiffuseAreaLight {
|
#[cfg(not(target_os = "cuda"))]
|
||||||
fn l(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
impl DiffuseAreaLight {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn new(
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium_interface: MediumInterface,
|
||||||
|
le: Spectrum,
|
||||||
|
scale: Float,
|
||||||
|
shape: Shape,
|
||||||
|
alpha: *const GPUFloatTexture,
|
||||||
|
image: *const Image,
|
||||||
|
image_color_space: *const RGBColorSpace,
|
||||||
|
two_sided: bool,
|
||||||
|
) -> Self {
|
||||||
|
let is_constant_zero = match &alpha {
|
||||||
|
GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (light_type, stored_alpha) = if is_constant_zero {
|
||||||
|
(LightType::DeltaPosition, None)
|
||||||
|
} else {
|
||||||
|
(LightType::Area, Some(alpha))
|
||||||
|
};
|
||||||
|
|
||||||
|
let base = LightBase::new(light_type, &render_from_light, &medium_interface);
|
||||||
|
|
||||||
|
let lemit = LightBase::lookup_spectrum(&le);
|
||||||
|
if let Some(im) = &image {
|
||||||
|
let desc = im
|
||||||
|
.get_channel_desc(&["R", "G", "B"])
|
||||||
|
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
|
||||||
|
|
||||||
|
assert_eq!(3, desc.size(), "Image channel description size mismatch");
|
||||||
|
assert!(
|
||||||
|
desc.is_identity(),
|
||||||
|
"Image channel description is not identity"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
image_color_space.is_some(),
|
||||||
|
"Image provided but ColorSpace is missing"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
|
||||||
|
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
|
||||||
|
println!(
|
||||||
|
"Scaling detected in rendering to light space transformation! \
|
||||||
|
The system has numerous assumptions, implicit and explicit, \
|
||||||
|
that this transform will have no scale factors in it. \
|
||||||
|
Proceed at your own risk; your image may have errors."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
area: shape.area(),
|
||||||
|
shape,
|
||||||
|
alpha: stored_alpha,
|
||||||
|
two_sided,
|
||||||
|
lemit,
|
||||||
|
scale,
|
||||||
|
image,
|
||||||
|
image_color_space,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
if !self.two_sided && n.dot(wo) <= 0.0 {
|
if !self.two_sided && n.dot(wo) <= 0.0 {
|
||||||
return SampledSpectrum::new(0.0);
|
return SampledSpectrum::new(0.0);
|
||||||
}
|
}
|
||||||
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
||||||
spec.sample(lambda) * self.scale
|
spec.sample(lambda) * self.scale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn alpha_masked(&self, intr: &Interaction) -> bool {
|
||||||
|
let Some(alpha_tex) = &self.alpha else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let ctx = TextureEvalContext::from(intr);
|
||||||
|
let a = UniversalTextureEvaluator.evaluate_float(alpha_tex, &ctx);
|
||||||
|
if a >= 1.0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if a <= 0.0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
hash_float(&intr.p()) > a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LightTrait for DiffuseAreaLight {
|
||||||
|
fn base(&self) -> &LightBase {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_li(
|
||||||
|
&self,
|
||||||
|
ctx: &LightSampleContext,
|
||||||
|
u: Point2f,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
_allow_incomplete_pdf: bool,
|
||||||
|
) -> Option<LightLiSample> {
|
||||||
|
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
|
||||||
|
let ss = self.shape.sample_from_context(&shape_ctx, u)?;
|
||||||
|
let mut intr: SurfaceInteraction = ss.intr.as_ref().clone();
|
||||||
|
|
||||||
|
intr.common.medium_interface = Some(self.base.medium_interface.clone());
|
||||||
|
let p = intr.p();
|
||||||
|
let n = intr.n();
|
||||||
|
let uv = intr.uv;
|
||||||
|
|
||||||
|
let generic_intr = Interaction::Surface(intr);
|
||||||
|
if self.alpha_masked(&generic_intr) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wi = (p - ctx.p()).normalize();
|
||||||
|
let le = self.l(p, n, uv, -wi, lambda);
|
||||||
|
|
||||||
|
if le.is_black() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(LightLiSample::new(le, wi, ss.pdf, generic_intr))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
|
||||||
|
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.);
|
||||||
|
self.shape.pdf_from_context(&shape_ctx, wi)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn l(
|
||||||
|
&self,
|
||||||
|
p: Point3f,
|
||||||
|
n: Normal3f,
|
||||||
|
mut uv: Point2f,
|
||||||
|
w: Vector3f,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> SampledSpectrum {
|
||||||
|
if self.two_sided && n.dot(w.into()) < 0. {
|
||||||
|
return SampledSpectrum::new(0.);
|
||||||
|
}
|
||||||
|
let intr = Interaction::Surface(SurfaceInteraction::new_minimal(
|
||||||
|
Point3fi::new_from_point(p),
|
||||||
|
uv,
|
||||||
|
));
|
||||||
|
|
||||||
|
if self.alpha_masked(&intr) {
|
||||||
|
return SampledSpectrum::new(0.);
|
||||||
|
}
|
||||||
|
if let Some(image) = &self.image {
|
||||||
|
let mut rgb = RGB::default();
|
||||||
|
uv[1] = 1. - uv[1];
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb[c] = image.bilerp_channel(uv, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
let spec = RGBIlluminantSpectrum::new(
|
||||||
|
self.image_color_space.as_ref().unwrap(),
|
||||||
|
RGB::clamp_zero(rgb),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.scale * spec.sample(lambda)
|
||||||
|
} else {
|
||||||
|
self.scale * self.lemit.sample(lambda)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||||
|
let mut l = SampledSpectrum::new(0.);
|
||||||
|
if let Some(image) = &self.image {
|
||||||
|
for y in 0..image.resolution().y() {
|
||||||
|
for x in 0..image.resolution().x() {
|
||||||
|
let mut rgb = RGB::default();
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb[c] = image.get_channel(Point2i::new(x, y), c);
|
||||||
|
}
|
||||||
|
l += RGBIlluminantSpectrum::new(
|
||||||
|
self.image_color_space.as_ref().unwrap(),
|
||||||
|
RGB::clamp_zero(rgb),
|
||||||
|
)
|
||||||
|
.sample(&lambda);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l *= self.scale / (image.resolution().x() * image.resolution().y()) as Float;
|
||||||
|
} else {
|
||||||
|
l = self.lemit.sample(&lambda) * self.scale;
|
||||||
|
}
|
||||||
|
let two_side = if self.two_sided { 2. } else { 1. };
|
||||||
|
PI * two_side * self.area * l
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn bounds(&self) -> Option<LightBounds> {
|
||||||
|
let mut phi = 0.;
|
||||||
|
if let Some(image) = &self.image {
|
||||||
|
for y in 0..image.resolution.y() {
|
||||||
|
for x in 0..image.resolution.x() {
|
||||||
|
for c in 0..3 {
|
||||||
|
phi += image.get_channel(Point2i::new(x, y), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
phi = self.lemit.max_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
let nb = self.shape.normal_bounds();
|
||||||
|
Some(LightBounds::new(
|
||||||
|
&self.shape.bounds(),
|
||||||
|
nb.w,
|
||||||
|
phi,
|
||||||
|
nb.cos_theta,
|
||||||
|
(PI / 2.).cos(),
|
||||||
|
self.two_sided,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
shared/src/lights/distant.rs
Normal file
101
shared/src/lights/distant.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f};
|
||||||
|
use crate::core::interaction::{Interaction, InteractionData};
|
||||||
|
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
|
||||||
|
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::{Float, PI};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct DistantLight {
|
||||||
|
pub base: LightBase,
|
||||||
|
pub lemit_coeffs: [Float; 32],
|
||||||
|
pub scale: Float,
|
||||||
|
pub scene_center: Point3f,
|
||||||
|
pub scene_radius: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DistantLight {
|
||||||
|
pub fn sample_li_base(
|
||||||
|
&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
|
||||||
|
}
|
||||||
|
}
|
||||||
109
shared/src/lights/goniometric.rs
Normal file
109
shared/src/lights/goniometric.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use crate::Float;
|
||||||
|
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point3f, Vector3f};
|
||||||
|
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightTrait};
|
||||||
|
use crate::core::medium::MediumInterface;
|
||||||
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::Transform;
|
||||||
|
use crate::utils::sampling::PiecewiseConstant2D;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GoniometricLight {
|
||||||
|
pub base: LightBase,
|
||||||
|
iemit: DenselySampledSpectrum,
|
||||||
|
scale: Float,
|
||||||
|
image: Image,
|
||||||
|
distrib: PiecewiseConstant2D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GoniometricLight {
|
||||||
|
pub fn new(
|
||||||
|
render_from_light: &Transform,
|
||||||
|
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 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!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn bounds(&self) -> Option<LightBounds> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,30 +11,19 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use rayon::prelude::*;
|
use crate::images::{PixelFormat, WrapMode};
|
||||||
|
|
||||||
use crate::image::{PixelFormat, WrapMode};
|
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
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)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct InfiniteUniformLight {
|
pub struct InfiniteUniformLight {
|
||||||
base: LightBaseData,
|
pub base: LightBase,
|
||||||
lemit: u32,
|
pub lemit: u32,
|
||||||
scale: Float,
|
pub scale: Float,
|
||||||
scene_center: Point3f,
|
pub scene_center: Point3f,
|
||||||
scene_radius: Float,
|
pub scene_radius: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl InfiniteUniformLight {
|
impl InfiniteUniformLight {
|
||||||
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
|
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
|
||||||
let base = LightBase::new(
|
let base = LightBase::new(
|
||||||
|
|
@ -83,10 +72,6 @@ impl LightTrait for InfiniteUniformLight {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
|
||||||
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pdf_li(
|
fn pdf_li(
|
||||||
&self,
|
&self,
|
||||||
_ctx: &LightSampleContext,
|
_ctx: &LightSampleContext,
|
||||||
|
|
@ -119,6 +104,9 @@ impl LightTrait for InfiniteUniformLight {
|
||||||
fn bounds(&self) -> Option<LightBounds> {
|
fn bounds(&self) -> Option<LightBounds> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||||
|
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -133,6 +121,7 @@ pub struct InfiniteImageLight {
|
||||||
compensated_distrib: PiecewiseConstant2D,
|
compensated_distrib: PiecewiseConstant2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl InfiniteImageLight {
|
impl InfiniteImageLight {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: TransformGeneric<Float>,
|
render_from_light: TransformGeneric<Float>,
|
||||||
|
|
@ -213,6 +202,7 @@ impl LightTrait for InfiniteImageLight {
|
||||||
fn base(&self) -> &LightBase {
|
fn base(&self) -> &LightBase {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_li(
|
fn sample_li(
|
||||||
&self,
|
&self,
|
||||||
ctx: &LightSampleContext,
|
ctx: &LightSampleContext,
|
||||||
|
|
@ -245,30 +235,6 @@ impl LightTrait for InfiniteImageLight {
|
||||||
Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr))
|
Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
|
||||||
let mut sum_l = SampledSpectrum::new(0.);
|
|
||||||
let width = self.image.resolution.x();
|
|
||||||
let height = self.image.resolution.y();
|
|
||||||
for v in 0..height {
|
|
||||||
for u in 0..width {
|
|
||||||
let mut rgb = RGB::default();
|
|
||||||
for c in 0..3 {
|
|
||||||
rgb[c] = self.image.get_channel_with_wrap(
|
|
||||||
Point2i::new(u, v),
|
|
||||||
c,
|
|
||||||
WrapMode::OctahedralSphere.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
sum_l += RGBIlluminantSpectrum::new(
|
|
||||||
self.image_color_space.as_ref(),
|
|
||||||
RGB::clamp_zero(rgb),
|
|
||||||
)
|
|
||||||
.sample(&lambda);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pdf_li(&self, _ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float {
|
fn pdf_li(&self, _ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float {
|
||||||
let w_light = self.base.render_from_light.apply_inverse_vector(wi);
|
let w_light = self.base.render_from_light.apply_inverse_vector(wi);
|
||||||
let uv = equal_area_sphere_to_square(w_light);
|
let uv = equal_area_sphere_to_square(w_light);
|
||||||
|
|
@ -301,22 +267,50 @@ impl LightTrait for InfiniteImageLight {
|
||||||
self.image_le(uv, lambda)
|
self.image_le(uv, lambda)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||||
|
let mut sum_l = SampledSpectrum::new(0.);
|
||||||
|
let width = self.image.resolution.x();
|
||||||
|
let height = self.image.resolution.y();
|
||||||
|
for v in 0..height {
|
||||||
|
for u in 0..width {
|
||||||
|
let mut rgb = RGB::default();
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb[c] = self.image.get_channel_with_wrap(
|
||||||
|
Point2i::new(u, v),
|
||||||
|
c,
|
||||||
|
WrapMode::OctahedralSphere.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sum_l += RGBIlluminantSpectrum::new(
|
||||||
|
self.image_color_space.as_ref(),
|
||||||
|
RGB::clamp_zero(rgb),
|
||||||
|
)
|
||||||
|
.sample(&lambda);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
fn preprocess(&mut self, scene_bounds: &Bounds3f) {
|
fn preprocess(&mut self, scene_bounds: &Bounds3f) {
|
||||||
let (scene_center, scene_radius) = scene_bounds.bounding_sphere();
|
let (scene_center, scene_radius) = scene_bounds.bounding_sphere();
|
||||||
self.scene_center = scene_center;
|
self.scene_center = scene_center;
|
||||||
self.scene_radius = scene_radius;
|
self.scene_radius = scene_radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
fn bounds(&self) -> Option<LightBounds> {
|
fn bounds(&self) -> Option<LightBounds> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct InfinitePortalLight {
|
pub struct InfinitePortalLight {
|
||||||
pub base: LightBaseData,
|
pub base: LightBase,
|
||||||
pub image: Image,
|
pub image: Image,
|
||||||
pub image_color_space: Arc<RGBColorSpace>,
|
pub image_color_space: RGBColorSpace,
|
||||||
pub scale: Float,
|
pub scale: Float,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub portal: [Point3f; 4],
|
pub portal: [Point3f; 4],
|
||||||
|
|
@ -326,10 +320,8 @@ pub struct InfinitePortalLight {
|
||||||
pub scene_radius: Float,
|
pub scene_radius: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
impl InfinitePortalLight {
|
impl InfinitePortalLight {
|
||||||
fn base(&self) -> &LightBase {
|
|
||||||
&self.base
|
|
||||||
}
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
render_from_light: TransformGeneric<Float>,
|
render_from_light: TransformGeneric<Float>,
|
||||||
equal_area_image: &Image,
|
equal_area_image: &Image,
|
||||||
|
|
@ -520,6 +512,7 @@ impl LightTrait for InfinitePortalLight {
|
||||||
fn base(&self) -> &LightBase {
|
fn base(&self) -> &LightBase {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_li(
|
fn sample_li(
|
||||||
&self,
|
&self,
|
||||||
ctx: &LightSampleContext,
|
ctx: &LightSampleContext,
|
||||||
|
|
@ -541,10 +534,6 @@ impl LightTrait for InfinitePortalLight {
|
||||||
Some(LightLiSample::new(l, wi, pdf, intr))
|
Some(LightLiSample::new(l, wi, pdf, intr))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
|
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
|
||||||
let Some((uv, duv_dw)) = self.image_from_render(wi) else {
|
let Some((uv, duv_dw)) = self.image_from_render(wi) else {
|
||||||
return 0.;
|
return 0.;
|
||||||
|
|
@ -576,10 +565,17 @@ impl LightTrait for InfinitePortalLight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
fn bounds(&self) -> Option<LightBounds> {
|
fn bounds(&self) -> Option<LightBounds> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,15 @@
|
||||||
pub mod diffuse;
|
pub mod diffuse;
|
||||||
|
pub mod distant;
|
||||||
|
pub mod goniometric;
|
||||||
pub mod infinite;
|
pub mod infinite;
|
||||||
|
pub mod point;
|
||||||
|
pub mod projection;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
|
pub mod spot;
|
||||||
|
|
||||||
|
pub use diffuse::DiffuseAreaLight;
|
||||||
|
pub use distant::DistantLight;
|
||||||
|
pub use goniometric::GoniometricLight;
|
||||||
|
pub use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
||||||
|
pub use point::PointLight;
|
||||||
|
pub use projection::ProjectionLight;
|
||||||
|
|
|
||||||
103
shared/src/lights/point.rs
Normal file
103
shared/src/lights/point.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
use crate::Float;
|
||||||
|
use crate::core::light::LightBaseData;
|
||||||
|
use crate::spectra::SampledSpectrum;
|
||||||
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PointLight {
|
||||||
|
pub base: LightBasea,
|
||||||
|
pub i:
|
||||||
|
pub scale: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LightTrait for PointLight {
|
||||||
|
fn sample_li_base(
|
||||||
|
&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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||||
|
4. * PI * self.scale * self.i.sample(&lambda)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
169
shared/src/lights/projection.rs
Normal file
169
shared/src/lights/projection.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
use crate::{
|
||||||
|
spectra::{RGB, RGBColorSpace},
|
||||||
|
utils::{Transform, sampling::PiecewiseConstant2D},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ProjectionLight {
|
||||||
|
pub base: LightBase,
|
||||||
|
pub scale: Float,
|
||||||
|
pub hither: Float,
|
||||||
|
pub screen_bounds: Bounds2f,
|
||||||
|
pub screen_from_light: Transform,
|
||||||
|
pub light_from_screen: Transform,
|
||||||
|
pub image_id: u32,
|
||||||
|
pub a: Float,
|
||||||
|
pub distrib: PiecewiseConstant2D,
|
||||||
|
pub image_color_space: *const RGBColorSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectionLight {
|
||||||
|
pub fn new(
|
||||||
|
render_from_light: Transform,
|
||||||
|
medium_interface: MediumInterface,
|
||||||
|
image_id: u32,
|
||||||
|
image_color_space: RGBColorSpace,
|
||||||
|
scale: Float,
|
||||||
|
fov: Float,
|
||||||
|
) -> Self {
|
||||||
|
let base = LightBase::new(
|
||||||
|
LightType::DeltaPosition,
|
||||||
|
&render_from_light,
|
||||||
|
&medium_interface,
|
||||||
|
);
|
||||||
|
let image = Image::new();
|
||||||
|
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_id,
|
||||||
|
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 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 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 preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> Option<LightBounds> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use super::{
|
|
||||||
Bounds3f, Float, Light, LightBounds, LightSampleContext, LightTrait, Normal3f, PI, Point3f,
|
|
||||||
SampledSpectrum, SampledWavelengths, Vector3f, VectorLike, safe_sqrt, square,
|
|
||||||
};
|
|
||||||
use crate::core::geometry::primitives::OctahedralVector;
|
use crate::core::geometry::primitives::OctahedralVector;
|
||||||
|
use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike};
|
||||||
use crate::core::geometry::{DirectionCone, Normal};
|
use crate::core::geometry::{DirectionCone, Normal};
|
||||||
use crate::utils::math::{clamp, lerp, sample_discrete};
|
use crate::utils::math::{clamp, lerp, sample_discrete};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::core::pbrt::ONE_MINUS_EPSILON;
|
use crate::core::light::{LightBounds, LightSampleContext};
|
||||||
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::math::{safe_sqrt, square};
|
||||||
use crate::utils::sampling::AliasTable;
|
use crate::utils::sampling::AliasTable;
|
||||||
|
use crate::{Float, ONE_MINUS_EPSILON, PI};
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
|
|
||||||
111
shared/src/lights/spot.rs
Normal file
111
shared/src/lights/spot.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
use crate::core::light::{LightBase, LightLiSample, LightSampleContext, LightTrait};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct SpotLight {
|
||||||
|
pub base: LightBase,
|
||||||
|
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 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!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
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.)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::Float;
|
use crate::Float;
|
||||||
|
|
||||||
|
pub const CIE_Y_INTEGRAL: Float = 106.856895;
|
||||||
|
|
||||||
pub const CIE_SAMPLES: usize = 471;
|
pub const CIE_SAMPLES: usize = 471;
|
||||||
pub const CIE_X: [Float; CIE_SAMPLES] = [
|
pub const CIE_X: [Float; CIE_SAMPLES] = [
|
||||||
0.0001299000,
|
0.0001299000,
|
||||||
|
|
@ -1425,6 +1427,112 @@ pub const CIE_Z: [Float; CIE_SAMPLES] = [
|
||||||
0.000000000000,
|
0.000000000000,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const D65_NORM: Float = 10566.864005283874576;
|
||||||
|
|
||||||
|
macro_rules! N {
|
||||||
|
($x:literal) => {
|
||||||
|
($x as Float) / D65_NORM
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CIE_D65: [Float; 95] = [
|
||||||
|
N!(46.6383),
|
||||||
|
N!(49.3637),
|
||||||
|
N!(52.0891),
|
||||||
|
N!(51.0323),
|
||||||
|
N!(49.9755),
|
||||||
|
N!(52.3118),
|
||||||
|
N!(54.6482),
|
||||||
|
N!(68.7015),
|
||||||
|
N!(82.7549),
|
||||||
|
N!(87.1204),
|
||||||
|
N!(91.486),
|
||||||
|
N!(92.4589),
|
||||||
|
N!(93.4318),
|
||||||
|
N!(90.057),
|
||||||
|
N!(86.6823),
|
||||||
|
N!(95.7736),
|
||||||
|
N!(104.865),
|
||||||
|
N!(110.936),
|
||||||
|
N!(117.008),
|
||||||
|
N!(117.41),
|
||||||
|
N!(117.812),
|
||||||
|
N!(116.336),
|
||||||
|
N!(114.861),
|
||||||
|
N!(115.392),
|
||||||
|
N!(115.923),
|
||||||
|
N!(112.367),
|
||||||
|
N(108.811),
|
||||||
|
N!(109.082),
|
||||||
|
N!(109.354),
|
||||||
|
N!(108.578),
|
||||||
|
N!(107.802),
|
||||||
|
N!(106.296),
|
||||||
|
N!(104.79),
|
||||||
|
N!(106.239),
|
||||||
|
N!(107.689),
|
||||||
|
N!(106.047),
|
||||||
|
N!(104.405),
|
||||||
|
N!(104.225),
|
||||||
|
N!(104.046),
|
||||||
|
N!(102.023),
|
||||||
|
N!(100.0),
|
||||||
|
N!(98.1671),
|
||||||
|
N!(96.3342),
|
||||||
|
N!(96.0611),
|
||||||
|
N!(95.788),
|
||||||
|
N!(92.2368),
|
||||||
|
N!(88.6856),
|
||||||
|
N!(89.3459),
|
||||||
|
N!(90.0062),
|
||||||
|
N!(89.8026),
|
||||||
|
N!(89.5991),
|
||||||
|
N!(88.6489),
|
||||||
|
N!(87.6987),
|
||||||
|
N!(85.4936),
|
||||||
|
N!(83.2886),
|
||||||
|
N!(83.4939),
|
||||||
|
N!(83.6992),
|
||||||
|
N!(81.863),
|
||||||
|
N!(80.0268),
|
||||||
|
N!(80.1207),
|
||||||
|
N!(80.2146),
|
||||||
|
N!(81.2462),
|
||||||
|
N!(82.2778),
|
||||||
|
N!(80.281),
|
||||||
|
N!(78.2842),
|
||||||
|
N!(74.0027),
|
||||||
|
N!(69.7213),
|
||||||
|
N!(70.6652),
|
||||||
|
N!(71.6091),
|
||||||
|
N!(72.979),
|
||||||
|
N!(74.349),
|
||||||
|
N!(67.9765),
|
||||||
|
N!(61.604),
|
||||||
|
N!(65.7448),
|
||||||
|
N!(69.8856),
|
||||||
|
N!(72.4863),
|
||||||
|
N!(75.087),
|
||||||
|
N!(69.3398),
|
||||||
|
N!(63.5927),
|
||||||
|
N!(55.0054),
|
||||||
|
N!(46.4182),
|
||||||
|
N!(56.6118),
|
||||||
|
N!(66.8054),
|
||||||
|
N!(65.0941),
|
||||||
|
N!(63.3828),
|
||||||
|
N!(63.8434),
|
||||||
|
N!(64.304),
|
||||||
|
N!(61.8779),
|
||||||
|
N!(59.4519),
|
||||||
|
N!(55.7054),
|
||||||
|
N!(51.959),
|
||||||
|
N!(54.6998),
|
||||||
|
N!(57.4406),
|
||||||
|
N!(58.8765),
|
||||||
|
N!(60.3125),
|
||||||
|
];
|
||||||
|
|
||||||
pub const CIE_LAMBDA: [i32; CIE_SAMPLES] = [
|
pub const CIE_LAMBDA: [i32; CIE_SAMPLES] = [
|
||||||
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378,
|
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378,
|
||||||
379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
|
379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use super::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ};
|
use crate::core::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ};
|
||||||
use crate::core::geometry::Point2f;
|
use crate::core::geometry::Point2f;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, Spectrum};
|
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
|
||||||
use crate::utils::math::SquareMatrix;
|
use crate::utils::math::SquareMatrix3f;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
|
@ -10,65 +10,32 @@ use std::cmp::{Eq, PartialEq};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct StandardColorSpaces {
|
||||||
|
pub srgb: *const RGBColorSpace,
|
||||||
|
pub dci_p3: *const RGBColorSpace,
|
||||||
|
pub rec2020: *const RGBColorSpace,
|
||||||
|
pub aces2065_1: *const RGBColorSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RGBColorSpace {
|
pub struct RGBColorSpace {
|
||||||
pub r: Point2f,
|
pub r: Point2f,
|
||||||
pub g: Point2f,
|
pub g: Point2f,
|
||||||
pub b: Point2f,
|
pub b: Point2f,
|
||||||
pub w: Point2f,
|
pub w: Point2f,
|
||||||
pub illuminant: Spectrum,
|
pub illuminant: DenselySampledSpectrum,
|
||||||
pub rgb_to_spectrum_table: Arc<RGBToSpectrumTable>,
|
pub rgb_to_spectrum_table: *const RGBToSpectrumTable,
|
||||||
pub xyz_from_rgb: SquareMatrix<Float, 3>,
|
pub xyz_from_rgb: SquareMatrix3f,
|
||||||
pub rgb_from_xyz: SquareMatrix<Float, 3>,
|
pub rgb_from_xyz: SquareMatrix3f,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for RGBColorSpace {}
|
||||||
|
unsafe impl Sync for RGBColorSpace {}
|
||||||
|
|
||||||
impl RGBColorSpace {
|
impl RGBColorSpace {
|
||||||
pub fn new(
|
|
||||||
r: Point2f,
|
|
||||||
g: Point2f,
|
|
||||||
b: Point2f,
|
|
||||||
illuminant: Spectrum,
|
|
||||||
rgb_to_spectrum_table: RGBToSpectrumTable,
|
|
||||||
) -> Result<Self, Box<dyn Error>> {
|
|
||||||
let w_xyz: XYZ = illuminant.to_xyz();
|
|
||||||
let w = w_xyz.xy();
|
|
||||||
let r_xyz = XYZ::from_xyy(r, Some(1.0));
|
|
||||||
let g_xyz = XYZ::from_xyy(g, Some(1.0));
|
|
||||||
let b_xyz = XYZ::from_xyy(b, Some(1.0));
|
|
||||||
let rgb_values = [
|
|
||||||
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
|
|
||||||
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
|
|
||||||
[r_xyz.z(), g_xyz.z(), b_xyz.z()],
|
|
||||||
];
|
|
||||||
let rgb = SquareMatrix::new(rgb_values);
|
|
||||||
let c: RGB = rgb.inverse()? * w_xyz;
|
|
||||||
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
|
|
||||||
let rgb_from_xyz = xyz_from_rgb
|
|
||||||
.inverse()
|
|
||||||
.expect("XYZ from RGB matrix is singular");
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
w,
|
|
||||||
illuminant,
|
|
||||||
rgb_to_spectrum_table: Arc::new(rgb_to_spectrum_table),
|
|
||||||
xyz_from_rgb,
|
|
||||||
rgb_from_xyz,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_named(name: &str) -> Result<Arc<RGBColorSpace>, String> {
|
|
||||||
match name.to_lowercase().as_str() {
|
|
||||||
"aces2065-1" => Ok(Self::aces2065_1().clone()),
|
|
||||||
"rec2020" => Ok(Self::rec2020().clone()),
|
|
||||||
"dci-p3" => Ok(Self::dci_p3().clone()),
|
|
||||||
"srgb" => Ok(Self::srgb().clone()),
|
|
||||||
_ => Err(format!("Color space '{}' not found", name)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
|
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
|
||||||
self.xyz_from_rgb * rgb
|
self.xyz_from_rgb * rgb
|
||||||
}
|
}
|
||||||
|
|
@ -81,71 +48,13 @@ impl RGBColorSpace {
|
||||||
self.rgb_to_spectrum_table.to_polynomial(rgb)
|
self.rgb_to_spectrum_table.to_polynomial(rgb)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix<Float, 3> {
|
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f {
|
||||||
if self == other {
|
if self == other {
|
||||||
return SquareMatrix::default();
|
return SquareMatrix3f::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rgb_from_xyz * other.xyz_from_rgb
|
self.rgb_from_xyz * other.xyz_from_rgb
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn srgb() -> &'static Arc<Self> {
|
|
||||||
static SRGB_SPACE: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
|
||||||
let r = Point2f::new(0.64, 0.33);
|
|
||||||
let g = Point2f::new(0.30, 0.60);
|
|
||||||
let b = Point2f::new(0.15, 0.06);
|
|
||||||
|
|
||||||
let illuminant = Spectrum::std_illuminant_d65();
|
|
||||||
let table = RGBToSpectrumTable::srgb();
|
|
||||||
|
|
||||||
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
|
||||||
});
|
|
||||||
|
|
||||||
&SRGB_SPACE
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dci_p3() -> &'static Arc<Self> {
|
|
||||||
static DCI_P3: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
|
||||||
let r = Point2f::new(0.680, 0.320);
|
|
||||||
let g = Point2f::new(0.265, 0.690);
|
|
||||||
let b = Point2f::new(0.150, 0.060);
|
|
||||||
let illuminant = Spectrum::std_illuminant_d65();
|
|
||||||
|
|
||||||
let table = RGBToSpectrumTable::dci_p3();
|
|
||||||
|
|
||||||
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
|
||||||
});
|
|
||||||
&DCI_P3
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rec2020() -> &'static Arc<Self> {
|
|
||||||
static REC2020: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
|
||||||
let r = Point2f::new(0.708, 0.292);
|
|
||||||
let g = Point2f::new(0.170, 0.797);
|
|
||||||
let b = Point2f::new(0.131, 0.046);
|
|
||||||
let illuminant = Spectrum::std_illuminant_d65();
|
|
||||||
|
|
||||||
let table = RGBToSpectrumTable::rec2020();
|
|
||||||
|
|
||||||
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
|
||||||
});
|
|
||||||
&REC2020
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn aces2065_1() -> &'static Arc<Self> {
|
|
||||||
static ACES: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
|
||||||
let r = Point2f::new(0.7347, 0.2653);
|
|
||||||
let g = Point2f::new(0.0000, 1.0000);
|
|
||||||
let b = Point2f::new(0.0001, -0.0770);
|
|
||||||
// ACES uses D60
|
|
||||||
let illuminant = Spectrum::std_illuminant_d65();
|
|
||||||
|
|
||||||
let table = RGBToSpectrumTable::aces2065_1();
|
|
||||||
|
|
||||||
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
|
|
||||||
});
|
|
||||||
&ACES
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for RGBColorSpace {
|
impl PartialEq for RGBColorSpace {
|
||||||
|
|
@ -154,6 +63,6 @@ impl PartialEq for RGBColorSpace {
|
||||||
&& self.g == other.g
|
&& self.g == other.g
|
||||||
&& self.b == other.b
|
&& self.b == other.b
|
||||||
&& self.w == other.w
|
&& self.w == other.w
|
||||||
&& Arc::ptr_eq(&self.rgb_to_spectrum_table, &other.rgb_to_spectrum_table)
|
&& self.rgb_to_spectrum_table == other.rgb_to_spectrum_table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
use super::Spectrum;
|
|
||||||
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
|
||||||
use super::simple::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
|
|
||||||
use crate::core::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, CIE_X, CIE_Y, CIE_Z, N_CIES};
|
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::spectra::{BlackbodySpectrum, SpectrumTrait};
|
|
||||||
use crate::utils::math::square;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
fn create_cie_spectrum(data: &[Float]) -> Spectrum {
|
|
||||||
let pls = PiecewiseLinearSpectrum::from_interleaved(data, false);
|
|
||||||
|
|
||||||
let dss = DenselySampledSpectrum::from_spectrum(&Spectrum::PiecewiseLinear(pls));
|
|
||||||
|
|
||||||
Spectrum::DenselySampled(dss)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_cie_d(temperature: Float) -> DenselySampledSpectrum {
|
|
||||||
let cct = temperature * 1.4388 / 1.4380;
|
|
||||||
if cct < 4000.0 {
|
|
||||||
let bb = BlackbodySpectrum::new(cct);
|
|
||||||
return DenselySampledSpectrum::from_function(
|
|
||||||
|lambda| bb.evaluate(lambda),
|
|
||||||
LAMBDA_MIN,
|
|
||||||
LAMBDA_MAX,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let x = if cct < 7000. {
|
|
||||||
-4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063
|
|
||||||
} else {
|
|
||||||
-2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704
|
|
||||||
};
|
|
||||||
let y = -3. * x + 2.87 * x - 0.275;
|
|
||||||
|
|
||||||
let m = 0.0241 + 0.2562 * x - 0.7341 * y;
|
|
||||||
let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m;
|
|
||||||
let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m;
|
|
||||||
|
|
||||||
let values: Vec<Float> = (0..N_CIES)
|
|
||||||
.map(|i| (CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let dpls = PiecewiseLinearSpectrum {
|
|
||||||
lambdas: CIE_S_LAMBDA.to_vec(),
|
|
||||||
values,
|
|
||||||
};
|
|
||||||
|
|
||||||
DenselySampledSpectrum::from_spectrum(&Spectrum::PiecewiseLinear(dpls))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cie_x() -> &'static Spectrum {
|
|
||||||
static X: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_X));
|
|
||||||
&X
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cie_y() -> &'static Spectrum {
|
|
||||||
static Y: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_Y));
|
|
||||||
&Y
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cie_z() -> &'static Spectrum {
|
|
||||||
static Z: Lazy<Spectrum> = Lazy::new(|| create_cie_spectrum(&CIE_Z));
|
|
||||||
&Z
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +1,13 @@
|
||||||
pub mod cie;
|
pub mod cie;
|
||||||
pub mod color;
|
|
||||||
pub mod colorspace;
|
pub mod colorspace;
|
||||||
pub mod data;
|
|
||||||
pub mod rgb;
|
pub mod rgb;
|
||||||
pub mod sampled;
|
pub mod sampled;
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
|
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use enum_dispatch::enum_dispatch;
|
|
||||||
|
|
||||||
pub use color::{ColorEncoding, RGB, RGBSigmoidPolynomial, XYZ};
|
|
||||||
pub use colorspace::RGBColorSpace;
|
pub use colorspace::RGBColorSpace;
|
||||||
pub use data::*;
|
|
||||||
pub use rgb::*;
|
pub use rgb::*;
|
||||||
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
|
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
|
||||||
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||||
pub use simple::*;
|
pub use simple::*;
|
||||||
//
|
|
||||||
#[enum_dispatch]
|
|
||||||
pub trait SpectrumTrait: Copy {
|
|
||||||
fn evaluate(&self, lambda: Float) -> Float;
|
|
||||||
fn max_value(&self) -> Float;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[cfg(not(target_arch = "spirv"))]
|
|
||||||
// impl SpectrumTrait for std::sync::Arc<DenselySampledSpectrum> {
|
|
||||||
// fn evaluate(&self, lambda: Float) -> Float {
|
|
||||||
// (**self).evaluate(lambda)
|
|
||||||
// }
|
|
||||||
// fn max_value(&self) -> Float {
|
|
||||||
// (**self).max_value()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// #[cfg(target_arch = "spirv")] // or target_os = "cuda"
|
|
||||||
// impl SpectrumTrait for u32 {
|
|
||||||
// fn evaluate(&self, lambda: Float) -> Float {
|
|
||||||
// // Here you would call a global function that accesses
|
|
||||||
// // a static buffer of spectra data
|
|
||||||
// crate::gpu::lookup_global_spectrum(*self, lambda)
|
|
||||||
// }
|
|
||||||
// fn max_value(&self) -> Float {
|
|
||||||
// crate::gpu::lookup_global_spectrum_max(*self)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
#[enum_dispatch(SpectrumTrait)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Spectrum {
|
|
||||||
Constant(ConstantSpectrum),
|
|
||||||
DenselySampled(DenselySampledSpectrum),
|
|
||||||
PiecewiseLinear(PiecewiseLinearSpectrum),
|
|
||||||
Blackbody(BlackbodySpectrum),
|
|
||||||
RGBAlbedo(RGBAlbedoSpectrum),
|
|
||||||
RGBIlluminant(RGBIlluminantSpectrum),
|
|
||||||
RGBUnbounded(RGBUnboundedSpectrum),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Spectrum {
|
|
||||||
pub fn std_illuminant_d65() -> Self {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_xyz(&self) -> XYZ {
|
|
||||||
let x = self.inner_product(data::cie_x());
|
|
||||||
let y = self.inner_product(data::cie_y());
|
|
||||||
let z = self.inner_product(data::cie_z());
|
|
||||||
|
|
||||||
XYZ::new(x, y, z) / CIE_Y_INTEGRAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_rgb(&self, cs: &RGBColorSpace) -> RGB {
|
|
||||||
let xyz = self.to_xyz();
|
|
||||||
cs.to_rgb(xyz)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sample(&self, wavelengths: &SampledWavelengths) -> SampledSpectrum {
|
|
||||||
SampledSpectrum::from_fn(|i| self.evaluate(wavelengths[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_product(&self, other: &Spectrum) -> Float {
|
|
||||||
let mut integral = 0.0;
|
|
||||||
// Iterate integer wavelengths.
|
|
||||||
for lambda in LAMBDA_MIN..=LAMBDA_MAX {
|
|
||||||
let l = lambda as Float;
|
|
||||||
integral += self.evaluate(l) * other.evaluate(l);
|
|
||||||
}
|
|
||||||
integral
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_constant(&self) -> bool {
|
|
||||||
matches!(self, Spectrum::Constant(_))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
use super::{
|
use super::{
|
||||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace,
|
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGBColorSpace,
|
||||||
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumTrait, XYZ,
|
SampledSpectrum, SampledWavelengths,
|
||||||
};
|
};
|
||||||
|
use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
|
||||||
|
use crate::core::spectrum::SpectrumTrait;
|
||||||
|
|
||||||
use crate::Float;
|
use crate::Float;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RGBAlbedoSpectrum {
|
pub struct RGBAlbedoSpectrum {
|
||||||
rsp: RGBSigmoidPolynomial,
|
pub rsp: RGBSigmoidPolynomial,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBAlbedoSpectrum {
|
impl RGBAlbedoSpectrum {
|
||||||
|
|
@ -16,14 +19,6 @@ impl RGBAlbedoSpectrum {
|
||||||
rsp: cs.to_rgb_coeffs(rgb),
|
rsp: cs.to_rgb_coeffs(rgb),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
|
||||||
let mut s = SampledSpectrum::default();
|
|
||||||
for i in 0..N_SPECTRUM_SAMPLES {
|
|
||||||
s[i] = self.rsp.evaluate(lambda[i]);
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpectrumTrait for RGBAlbedoSpectrum {
|
impl SpectrumTrait for RGBAlbedoSpectrum {
|
||||||
|
|
@ -36,10 +31,11 @@ impl SpectrumTrait for RGBAlbedoSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
pub struct UnboundedRGBSpectrum {
|
pub struct UnboundedRGBSpectrum {
|
||||||
scale: Float,
|
pub scale: Float,
|
||||||
rsp: RGBSigmoidPolynomial,
|
pub rsp: RGBSigmoidPolynomial,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnboundedRGBSpectrum {
|
impl UnboundedRGBSpectrum {
|
||||||
|
|
@ -68,38 +64,31 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct RGBIlluminantSpectrum {
|
pub struct RGBIlluminantSpectrum {
|
||||||
scale: Float,
|
pub scale: Float,
|
||||||
rsp: RGBSigmoidPolynomial,
|
pub rsp: RGBSigmoidPolynomial,
|
||||||
illuminant: DenselySampledSpectrum,
|
pub illuminant: DenselySampledSpectrum,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl RGBIlluminantSpectrum {
|
impl RGBIlluminantSpectrum {
|
||||||
// pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
||||||
// let illuminant = &cs.illuminant;
|
let illuminant = &cs.illuminant;
|
||||||
// let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
|
let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
|
||||||
// let m = rgb.max_component_value();
|
let m = rgb.max_component_value();
|
||||||
// let scale = 2. * m;
|
let scale = 2. * m;
|
||||||
// let rsp = cs.to_rgb_coeffs(if scale == 1. {
|
let rsp = cs.to_rgb_coeffs(if scale == 1. {
|
||||||
// rgb / scale
|
rgb / scale
|
||||||
// } else {
|
} else {
|
||||||
// RGB::new(0., 0., 0.)
|
RGB::new(0., 0., 0.)
|
||||||
// });
|
});
|
||||||
// Self {
|
Self {
|
||||||
// scale,
|
scale,
|
||||||
// rsp,
|
rsp,
|
||||||
// illuminant: Some(Arc::new(densely_sampled)),
|
illuminant,
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//
|
}
|
||||||
// pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
|
||||||
// if self.illuminant.is_none() {
|
|
||||||
// return SampledSpectrum::new(0.);
|
|
||||||
// }
|
|
||||||
// SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl SpectrumTrait for RGBIlluminantSpectrum {
|
impl SpectrumTrait for RGBIlluminantSpectrum {
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
|
|
@ -111,6 +100,13 @@ impl SpectrumTrait for RGBIlluminantSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
if self.illuminant.is_none() {
|
||||||
|
return SampledSpectrum::new(0.);
|
||||||
|
}
|
||||||
|
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
|
||||||
|
}
|
||||||
|
|
||||||
fn max_value(&self) -> Float {
|
fn max_value(&self) -> Float {
|
||||||
match &self.illuminant {
|
match &self.illuminant {
|
||||||
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(),
|
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(),
|
||||||
|
|
@ -126,8 +122,8 @@ pub struct RGBSpectrum {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RGBUnboundedSpectrum {
|
pub struct RGBUnboundedSpectrum {
|
||||||
scale: Float,
|
pub scale: Float,
|
||||||
rsp: RGBSigmoidPolynomial,
|
pub rsp: RGBSigmoidPolynomial,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RGBUnboundedSpectrum {
|
impl Default for RGBUnboundedSpectrum {
|
||||||
|
|
@ -155,10 +151,6 @@ impl RGBUnboundedSpectrum {
|
||||||
|
|
||||||
Self { scale, rsp }
|
Self { scale, rsp }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
|
||||||
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpectrumTrait for RGBUnboundedSpectrum {
|
impl SpectrumTrait for RGBUnboundedSpectrum {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::spectrum::StandardSpectra;
|
||||||
use crate::utils::math::{clamp, lerp};
|
use crate::utils::math::{clamp, lerp};
|
||||||
use std::ops::{
|
use std::ops::{
|
||||||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{cie_x, cie_y, cie_z};
|
|
||||||
|
|
||||||
pub const CIE_Y_INTEGRAL: Float = 106.856895;
|
pub const CIE_Y_INTEGRAL: Float = 106.856895;
|
||||||
|
|
||||||
pub const N_SPECTRUM_SAMPLES: usize = 1200;
|
pub const N_SPECTRUM_SAMPLES: usize = 4;
|
||||||
pub const LAMBDA_MIN: i32 = 360;
|
pub const LAMBDA_MIN: i32 = 360;
|
||||||
pub const LAMBDA_MAX: i32 = 830;
|
pub const LAMBDA_MAX: i32 = 830;
|
||||||
|
|
||||||
|
|
@ -43,10 +42,13 @@ impl SampledSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_vector(v: Vec<Float>) -> Self {
|
pub fn from_array(v: &[Float]) -> Self {
|
||||||
|
assert!(N_SPECTRUM_SAMPLES == v.len());
|
||||||
let mut values = [0.0; N_SPECTRUM_SAMPLES];
|
let mut values = [0.0; N_SPECTRUM_SAMPLES];
|
||||||
let count = v.len().min(N_SPECTRUM_SAMPLES);
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
values[..count].copy_from_slice(&v[..count]);
|
values[i] = v[i];
|
||||||
|
}
|
||||||
|
|
||||||
Self { values }
|
Self { values }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,8 +117,8 @@ impl SampledSpectrum {
|
||||||
SampledSpectrum::from_fn(|i| if b[i] != 0. { a[i] / b[i] } else { 0. })
|
SampledSpectrum::from_fn(|i| if b[i] != 0. { a[i] / b[i] } else { 0. })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn y(&self, lambda: &SampledWavelengths) -> Float {
|
pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float {
|
||||||
let ys = cie_y().sample(lambda);
|
let ys = std.cie_y().sample(lambda);
|
||||||
let pdf = lambda.pdf();
|
let pdf = lambda.pdf();
|
||||||
SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL
|
SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL
|
||||||
}
|
}
|
||||||
|
|
@ -294,8 +296,8 @@ impl Neg for SampledSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct SampledWavelengths {
|
pub struct SampledWavelengths {
|
||||||
pub lambda: [Float; N_SPECTRUM_SAMPLES],
|
pub lambda: [Float; N_SPECTRUM_SAMPLES],
|
||||||
pub pdf: [Float; N_SPECTRUM_SAMPLES],
|
pub pdf: [Float; N_SPECTRUM_SAMPLES],
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
|
use super::cie::*;
|
||||||
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
|
||||||
use crate::core::cie::*;
|
use crate::Float;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||||
use crate::spectra::{
|
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||||
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
use core::slice;
|
||||||
};
|
|
||||||
use crate::utils::file::read_float_file;
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ConstantSpectrum {
|
pub struct ConstantSpectrum {
|
||||||
c: Float,
|
pub c: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConstantSpectrum {
|
impl ConstantSpectrum {
|
||||||
|
|
@ -31,90 +29,61 @@ impl SpectrumTrait for ConstantSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct DenselySampledSpectrum {
|
pub struct DenselySampledSpectrum {
|
||||||
lambda_min: i32,
|
pub lambda_min: i32,
|
||||||
lambda_max: i32,
|
pub lambda_max: i32,
|
||||||
values: Vec<Float>,
|
pub values: *const Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for DenselySampledSpectrum {}
|
||||||
|
unsafe impl Sync for DenselySampledSpectrum {}
|
||||||
|
|
||||||
impl DenselySampledSpectrum {
|
impl DenselySampledSpectrum {
|
||||||
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
|
#[inline(always)]
|
||||||
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
|
fn as_slice(&self) -> &[Float] {
|
||||||
Self {
|
if self.values.is_null() {
|
||||||
lambda_min,
|
return &[];
|
||||||
lambda_max,
|
|
||||||
values: vec![0.0; n_values],
|
|
||||||
}
|
}
|
||||||
}
|
let len = (self.lambda_max - self.lambda_min + 1).max(0) as usize;
|
||||||
|
unsafe { slice::from_raw_parts(self.values, len) }
|
||||||
pub fn from_spectrum(spec: &Spectrum) -> Self {
|
|
||||||
let lambda_min = LAMBDA_MIN;
|
|
||||||
let lambda_max = LAMBDA_MAX;
|
|
||||||
let mut s = Self::new(lambda_min, lambda_max);
|
|
||||||
if s.values.is_empty() {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
for lambda in lambda_min..=lambda_max {
|
|
||||||
let index = (lambda - lambda_min) as usize;
|
|
||||||
s.values[index] = spec.evaluate(lambda as Float);
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_spectrum_with_range(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
|
|
||||||
let mut s = Self::new(lambda_min, lambda_max);
|
|
||||||
if s.values.is_empty() {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
for lambda in lambda_min..=lambda_max {
|
|
||||||
let index = (lambda - lambda_min) as usize;
|
|
||||||
s.values[index] = spec.evaluate(lambda as Float);
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(Float) -> Float,
|
|
||||||
{
|
|
||||||
let mut s = Self::new(lambda_min, lambda_max);
|
|
||||||
if s.values.is_empty() {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
for lambda in lambda_min..=lambda_max {
|
|
||||||
let index = (lambda - lambda_min) as usize;
|
|
||||||
s.values[index] = f(lambda as Float);
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
let mut s = SampledSpectrum::default();
|
let mut s = SampledSpectrum::default();
|
||||||
|
|
||||||
for i in 0..N_SPECTRUM_SAMPLES {
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize;
|
let offset = lambda[i].round() as i32 - self.lambda_min;
|
||||||
if offset >= self.values.len() {
|
let len = (self.lambda_max - self.lambda_min + 1) as i32;
|
||||||
s[i] = 0.;
|
|
||||||
|
if offset < 0 || offset >= len {
|
||||||
|
s[i] = 0.0;
|
||||||
} else {
|
} else {
|
||||||
s[i] = self.values[offset];
|
unsafe { s[i] = *self.values.add(offset as usize) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn min_component_value(&self) -> Float {
|
pub fn min_component_value(&self) -> Float {
|
||||||
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b))
|
self.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(Float::INFINITY, |a, &b| a.min(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_component_value(&self) -> Float {
|
pub fn max_component_value(&self) -> Float {
|
||||||
self.values
|
self.as_slice()
|
||||||
.iter()
|
.iter()
|
||||||
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn average(&self) -> Float {
|
pub fn average(&self) -> Float {
|
||||||
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
|
let slice = self.as_slice();
|
||||||
|
if slice.is_empty() {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
slice.iter().sum::<Float>() / (slice.len() as Float)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
||||||
|
|
@ -128,12 +97,6 @@ impl DenselySampledSpectrum {
|
||||||
}
|
}
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scale(&mut self, factor: Float) {
|
|
||||||
for v in &mut self.values {
|
|
||||||
*v *= factor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for DenselySampledSpectrum {
|
impl PartialEq for DenselySampledSpectrum {
|
||||||
|
|
@ -180,66 +143,16 @@ impl SpectrumTrait for DenselySampledSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PiecewiseLinearSpectrum {
|
pub struct PiecewiseLinearSpectrum {
|
||||||
pub lambdas: Vec<Float>,
|
pub lambdas: *const Float,
|
||||||
pub values: Vec<Float>,
|
pub values: *const Float,
|
||||||
|
pub count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PiecewiseLinearSpectrum {
|
unsafe impl Send for PiecewiseLinearSpectrum {}
|
||||||
pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self {
|
unsafe impl Sync for PiecewiseLinearSpectrum {}
|
||||||
if data.len() % 2 != 0 {
|
|
||||||
panic!("Interleaved data must have an even number of elements");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut temp: Vec<(Float, Float)> =
|
|
||||||
data.chunks(2).map(|chunk| (chunk[0], chunk[1])).collect();
|
|
||||||
|
|
||||||
temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
|
|
||||||
|
|
||||||
let (lambdas, values): (Vec<Float>, Vec<Float>) = temp.into_iter().unzip();
|
|
||||||
|
|
||||||
// Normalization left to the reader
|
|
||||||
// This usually involves integrating the curve and scaling 'values'
|
|
||||||
|
|
||||||
Self { lambdas, values }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(filepath: &str) -> Option<Self> {
|
|
||||||
let vals = match read_float_file(filepath) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if vals.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if vals.len() % 2 == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = vals.len() / 2;
|
|
||||||
let mut lambdas = Vec::with_capacity(count);
|
|
||||||
let mut values = Vec::with_capacity(count);
|
|
||||||
|
|
||||||
for (_, pair) in vals.chunks(2).enumerate() {
|
|
||||||
let curr_lambda = pair[0];
|
|
||||||
let curr_val = pair[1];
|
|
||||||
|
|
||||||
if let Some(&prev_lambda) = lambdas.last() {
|
|
||||||
if curr_lambda <= prev_lambda {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lambdas.push(curr_lambda);
|
|
||||||
values.push(curr_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PiecewiseLinearSpectrum { lambdas, values })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpectrumTrait for PiecewiseLinearSpectrum {
|
impl SpectrumTrait for PiecewiseLinearSpectrum {
|
||||||
fn evaluate(&self, lambda: Float) -> Float {
|
fn evaluate(&self, lambda: Float) -> Float {
|
||||||
|
|
@ -274,10 +187,11 @@ impl SpectrumTrait for PiecewiseLinearSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct BlackbodySpectrum {
|
pub struct BlackbodySpectrum {
|
||||||
temperature: Float,
|
pub temperature: Float,
|
||||||
normalization_factor: Float,
|
pub normalization_factor: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planck's Law
|
// Planck's Law
|
||||||
|
|
@ -331,150 +245,3 @@ impl SpectrumTrait for BlackbodySpectrum {
|
||||||
Self::planck_law(lambda_max, self.temperature)
|
Self::planck_law(lambda_max, self.temperature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
|
|
||||||
// A macro to reduce boilerplate and make the list readable
|
|
||||||
macro_rules! add {
|
|
||||||
($name:expr, $data:expr, $norm:expr) => {
|
|
||||||
m.insert(
|
|
||||||
$name.to_string(),
|
|
||||||
Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum::from_interleaved($data, $norm)),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
add!("stdillum-A", &CIE_ILLUM_A, true);
|
|
||||||
add!("stdillum-D50", &CIE_ILLUM_D5000, true);
|
|
||||||
add!("stdillum-D65", &CIE_ILLUM_D6500, true);
|
|
||||||
add!("stdillum-F1", &CIE_ILLUM_F1, true);
|
|
||||||
add!("stdillum-F2", &CIE_ILLUM_F2, true);
|
|
||||||
add!("stdillum-F3", &CIE_ILLUM_F3, true);
|
|
||||||
add!("stdillum-F4", &CIE_ILLUM_F4, true);
|
|
||||||
add!("stdillum-F5", &CIE_ILLUM_F5, true);
|
|
||||||
add!("stdillum-F6", &CIE_ILLUM_F6, true);
|
|
||||||
add!("stdillum-F7", &CIE_ILLUM_F7, true);
|
|
||||||
add!("stdillum-F8", &CIE_ILLUM_F8, true);
|
|
||||||
add!("stdillum-F9", &CIE_ILLUM_F9, true);
|
|
||||||
add!("stdillum-F10", &CIE_ILLUM_F10, true);
|
|
||||||
add!("stdillum-F11", &CIE_ILLUM_F11, true);
|
|
||||||
add!("stdillum-F12", &CIE_ILLUM_F12, true);
|
|
||||||
add!("illum-acesD60", &ACES_ILLUM_D60, true);
|
|
||||||
|
|
||||||
// --- Glasses ---
|
|
||||||
add!("glass-BK7", &GLASS_BK7_ETA, false);
|
|
||||||
add!("glass-BAF10", &GLASS_BAF10_ETA, false);
|
|
||||||
add!("glass-FK51A", &GLASS_FK51A_ETA, false);
|
|
||||||
add!("glass-LASF9", &GLASS_LASF9_ETA, false);
|
|
||||||
add!("glass-F5", &GLASS_SF5_ETA, false);
|
|
||||||
add!("glass-F10", &GLASS_SF10_ETA, false);
|
|
||||||
add!("glass-F11", &GLASS_SF11_ETA, false);
|
|
||||||
|
|
||||||
// --- Metals ---
|
|
||||||
add!("metal-Ag-eta", &AG_ETA, false);
|
|
||||||
add!("metal-Ag-k", &AG_K, false);
|
|
||||||
add!("metal-Al-eta", &AL_ETA, false);
|
|
||||||
add!("metal-Al-k", &AL_K, false);
|
|
||||||
add!("metal-Au-eta", &AU_ETA, false);
|
|
||||||
add!("metal-Au-k", &AU_K, false);
|
|
||||||
add!("metal-Cu-eta", &CU_ETA, false);
|
|
||||||
add!("metal-Cu-k", &CU_K, false);
|
|
||||||
add!("metal-CuZn-eta", &CUZN_ETA, false);
|
|
||||||
add!("metal-CuZn-k", &CUZN_K, false);
|
|
||||||
add!("metal-MgO-eta", &MGO_ETA, false);
|
|
||||||
add!("metal-MgO-k", &MGO_K, false);
|
|
||||||
add!("metal-TiO2-eta", &TIO2_ETA, false);
|
|
||||||
add!("metal-TiO2-k", &TIO2_K, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 100D ---
|
|
||||||
add!("canon_eos_100d_r", &CANON_EOS_100D_R, false);
|
|
||||||
add!("canon_eos_100d_g", &CANON_EOS_100D_G, false);
|
|
||||||
add!("canon_eos_100d_b", &CANON_EOS_100D_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 1DX MkII ---
|
|
||||||
add!("canon_eos_1dx_mkii_r", &CANON_EOS_1DX_MKII_R, false);
|
|
||||||
add!("canon_eos_1dx_mkii_g", &CANON_EOS_1DX_MKII_G, false);
|
|
||||||
add!("canon_eos_1dx_mkii_b", &CANON_EOS_1DX_MKII_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 200D ---
|
|
||||||
add!("canon_eos_200d_r", &CANON_EOS_200D_R, false);
|
|
||||||
add!("canon_eos_200d_g", &CANON_EOS_200D_G, false);
|
|
||||||
add!("canon_eos_200d_b", &CANON_EOS_200D_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 200D MkII ---
|
|
||||||
add!("canon_eos_200d_mkii_r", &CANON_EOS_200D_MKII_R, false);
|
|
||||||
add!("canon_eos_200d_mkii_g", &CANON_EOS_200D_MKII_G, false);
|
|
||||||
add!("canon_eos_200d_mkii_b", &CANON_EOS_200D_MKII_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 5D ---
|
|
||||||
add!("canon_eos_5d_r", &CANON_EOS_5D_R, false);
|
|
||||||
add!("canon_eos_5d_g", &CANON_EOS_5D_G, false);
|
|
||||||
add!("canon_eos_5d_b", &CANON_EOS_5D_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 5D MkII ---
|
|
||||||
add!("canon_eos_5d_mkii_r", &CANON_EOS_5D_MKII_R, false);
|
|
||||||
add!("canon_eos_5d_mkii_g", &CANON_EOS_5D_MKII_G, false);
|
|
||||||
add!("canon_eos_5d_mkii_b", &CANON_EOS_5D_MKII_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 5D MkIII ---
|
|
||||||
add!("canon_eos_5d_mkiii_r", &CANON_EOS_5D_MKIII_R, false);
|
|
||||||
add!("canon_eos_5d_mkiii_g", &CANON_EOS_5D_MKIII_G, false);
|
|
||||||
add!("canon_eos_5d_mkiii_b", &CANON_EOS_5D_MKIII_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 5D MkIV ---
|
|
||||||
add!("canon_eos_5d_mkiv_r", &CANON_EOS_5D_MKIV_R, false);
|
|
||||||
add!("canon_eos_5d_mkiv_g", &CANON_EOS_5D_MKIV_G, false);
|
|
||||||
add!("canon_eos_5d_mkiv_b", &CANON_EOS_5D_MKIV_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS 5DS ---
|
|
||||||
add!("canon_eos_5ds_r", &CANON_EOS_5DS_R, false);
|
|
||||||
add!("canon_eos_5ds_g", &CANON_EOS_5DS_G, false);
|
|
||||||
add!("canon_eos_5ds_b", &CANON_EOS_5DS_B, false);
|
|
||||||
|
|
||||||
// --- Canon EOS M ---
|
|
||||||
add!("canon_eos_m_r", &CANON_EOS_M_R, false);
|
|
||||||
add!("canon_eos_m_g", &CANON_EOS_M_G, false);
|
|
||||||
add!("canon_eos_m_b", &CANON_EOS_M_B, false);
|
|
||||||
|
|
||||||
// --- Hasselblad L1D 20C ---
|
|
||||||
add!("hasselblad_l1d_20c_r", &HASSELBLAD_L1D_20C_R, false);
|
|
||||||
add!("hasselblad_l1d_20c_g", &HASSELBLAD_L1D_20C_G, false);
|
|
||||||
add!("hasselblad_l1d_20c_b", &HASSELBLAD_L1D_20C_B, false);
|
|
||||||
|
|
||||||
// --- Nikon D810 ---
|
|
||||||
add!("nikon_d810_r", &NIKON_D810_R, false);
|
|
||||||
add!("nikon_d810_g", &NIKON_D810_G, false);
|
|
||||||
add!("nikon_d810_b", &NIKON_D810_B, false);
|
|
||||||
|
|
||||||
// --- Nikon D850 ---
|
|
||||||
add!("nikon_d850_r", &NIKON_D850_R, false);
|
|
||||||
add!("nikon_d850_g", &NIKON_D850_G, false);
|
|
||||||
add!("nikon_d850_b", &NIKON_D850_B, false);
|
|
||||||
|
|
||||||
// --- Sony ILCE 6400 ---
|
|
||||||
add!("sony_ilce_6400_r", &SONY_ILCE_6400_R, false);
|
|
||||||
add!("sony_ilce_6400_g", &SONY_ILCE_6400_G, false);
|
|
||||||
add!("sony_ilce_6400_b", &SONY_ILCE_6400_B, false);
|
|
||||||
|
|
||||||
// --- Sony ILCE 7M3 ---
|
|
||||||
add!("sony_ilce_7m3_r", &SONY_ILCE_7M3_R, false);
|
|
||||||
add!("sony_ilce_7m3_g", &SONY_ILCE_7M3_G, false);
|
|
||||||
add!("sony_ilce_7m3_b", &SONY_ILCE_7M3_B, false);
|
|
||||||
|
|
||||||
// --- Sony ILCE 7RM3 ---
|
|
||||||
add!("sony_ilce_7rm3_r", &SONY_ILCE_7RM3_R, false);
|
|
||||||
add!("sony_ilce_7rm3_g", &SONY_ILCE_7RM3_G, false);
|
|
||||||
add!("sony_ilce_7rm3_b", &SONY_ILCE_7RM3_B, false);
|
|
||||||
|
|
||||||
// --- Sony ILCE 9 ---
|
|
||||||
add!("sony_ilce_9_r", &SONY_ILCE_9_R, false);
|
|
||||||
add!("sony_ilce_9_g", &SONY_ILCE_9_G, false);
|
|
||||||
add!("sony_ilce_9_b", &SONY_ILCE_9_B, false);
|
|
||||||
|
|
||||||
m
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn get_named_spectrum(name: &str) -> Option<Spectrum> {
|
|
||||||
NAMED_SPECTRA.get(name).cloned()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,33 @@
|
||||||
|
use crate::core::geometry::Vector3f;
|
||||||
|
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
|
||||||
|
use crate::utils::Ptr;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct GPUFloatMixTexture {
|
pub struct GPUFloatMixTexture {
|
||||||
tex1: GPUFloatTexture,
|
pub tex1: Ptr<GPUFloatTexture>,
|
||||||
tex2: GPUFloatTexture,
|
pub tex2: Ptr<GPUFloatTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct GPUFloatDirectionMixTexture {
|
pub struct GPUFloatDirectionMixTexture {
|
||||||
tex1: GPUFloatTexture,
|
pub tex1: Ptr<GPUFloatTexture>,
|
||||||
tex2: GPUFloatTexture,
|
pub tex2: Ptr<GPUFloatTexture>,
|
||||||
dir: Vector3f,
|
pub dir: Vector3f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct GPUSpectrumMixTexture {
|
||||||
|
pub tex1: Ptr<GPUSpectrumTexture>,
|
||||||
|
pub tex2: Ptr<GPUSpectrumTexture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct GPUSpectrumDirectionMixTexture {
|
||||||
|
pub tex1: Ptr<GPUSpectrumTexture>,
|
||||||
|
pub tex2: Ptr<GPUSpectrumTexture>,
|
||||||
|
pub dir: Vector3f,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub mod image;
|
||||||
pub mod marble;
|
pub mod marble;
|
||||||
pub mod mix;
|
pub mod mix;
|
||||||
pub mod ptex;
|
pub mod ptex;
|
||||||
|
pub mod scaled;
|
||||||
pub mod windy;
|
pub mod windy;
|
||||||
pub mod wrinkled;
|
pub mod wrinkled;
|
||||||
|
|
||||||
|
|
@ -19,5 +20,6 @@ pub use image::*;
|
||||||
pub use marble::*;
|
pub use marble::*;
|
||||||
pub use mix::*;
|
pub use mix::*;
|
||||||
pub use ptex::*;
|
pub use ptex::*;
|
||||||
|
pub use scaled::*;
|
||||||
pub use windy::*;
|
pub use windy::*;
|
||||||
pub use wrinkled::*;
|
pub use wrinkled::*;
|
||||||
|
|
|
||||||
16
shared/src/textures/scaled.rs
Normal file
16
shared/src/textures/scaled.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
|
||||||
|
use crate::utils::Ptr;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct GPUFloatScaledTexture {
|
||||||
|
tex: Ptr<GPUFloatTexture>,
|
||||||
|
scale: Ptr<GPUFloatTexture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct GPUSpectrumScaledTexture {
|
||||||
|
tex: Ptr<GPUSpectrumTexture>,
|
||||||
|
scale: Ptr<GPUFloatTexture>,
|
||||||
|
}
|
||||||
|
|
@ -180,31 +180,40 @@ impl<T> IndexMut<(i32, i32)> for Array2D<T> {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SampledGrid<T> {
|
pub struct SampledGrid<T> {
|
||||||
values: Vec<T>,
|
pub values: *const T,
|
||||||
nx: i32,
|
pub nx: i32,
|
||||||
ny: i32,
|
pub ny: i32,
|
||||||
nz: i32,
|
pub nz: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Sync> Sync for SampledGrid<T> {}
|
||||||
|
unsafe impl<T: Send> Send for SampledGrid<T> {}
|
||||||
|
|
||||||
impl<T> SampledGrid<T> {
|
impl<T> SampledGrid<T> {
|
||||||
pub fn new(values: Vec<T>, nx: i32, ny: i32, nz: i32) -> Self {
|
#[cfg(not(target_os = "cuda"))]
|
||||||
assert_eq!(
|
pub fn new(slice: &[T], nx: i32, ny: i32, nz: i32) -> Self {
|
||||||
values.len(),
|
assert_eq!(slice.len(), (nx * ny * nz) as usize);
|
||||||
(nx * ny * nz) as usize,
|
Self {
|
||||||
"Grid dimensions do not match data size"
|
values: slice.as_ptr(),
|
||||||
);
|
nx,
|
||||||
Self { values, nx, ny, nz }
|
ny,
|
||||||
|
nz,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
values: Vec::new(),
|
values: core::ptr::null(),
|
||||||
nx: 0,
|
nx: 0,
|
||||||
ny: 0,
|
ny: 0,
|
||||||
nz: 0,
|
nz: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
!self.values.is_null() && self.nx > 0 && self.ny > 0 && self.nz > 0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bytes_allocated(&self) -> usize {
|
pub fn bytes_allocated(&self) -> usize {
|
||||||
self.values.len() * std::mem::size_of::<T>()
|
self.values.len() * std::mem::size_of::<T>()
|
||||||
}
|
}
|
||||||
|
|
@ -212,9 +221,11 @@ impl<T> SampledGrid<T> {
|
||||||
pub fn x_size(&self) -> i32 {
|
pub fn x_size(&self) -> i32 {
|
||||||
self.nx
|
self.nx
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn y_size(&self) -> i32 {
|
pub fn y_size(&self) -> i32 {
|
||||||
self.ny
|
self.ny
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn z_size(&self) -> i32 {
|
pub fn z_size(&self) -> i32 {
|
||||||
self.nz
|
self.nz
|
||||||
}
|
}
|
||||||
|
|
@ -224,7 +235,11 @@ impl<T> SampledGrid<T> {
|
||||||
F: Fn(&T) -> U,
|
F: Fn(&T) -> U,
|
||||||
U: Default,
|
U: Default,
|
||||||
{
|
{
|
||||||
let sample_bounds = Bounds3i::from_points(
|
if !self.is_valid() {
|
||||||
|
return U::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let sample_bounds = Bounds3i::new(
|
||||||
Point3i::new(0, 0, 0),
|
Point3i::new(0, 0, 0),
|
||||||
Point3i::new(self.nx, self.ny, self.nz),
|
Point3i::new(self.nx, self.ny, self.nz),
|
||||||
);
|
);
|
||||||
|
|
@ -234,7 +249,10 @@ impl<T> SampledGrid<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = (p.z() * self.ny + p.y()) * self.nx + p.x();
|
let idx = (p.z() * self.ny + p.y()) * self.nx + p.x();
|
||||||
convert(&self.values[idx as usize])
|
unsafe {
|
||||||
|
let val = &*self.values.add(idx as usize);
|
||||||
|
convert(val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_int(&self, p: Point3i) -> T
|
pub fn lookup_int(&self, p: Point3i) -> T
|
||||||
|
|
@ -249,6 +267,10 @@ impl<T> SampledGrid<T> {
|
||||||
F: Fn(&T) -> U + Copy,
|
F: Fn(&T) -> U + Copy,
|
||||||
U: Interpolatable + Default,
|
U: Interpolatable + Default,
|
||||||
{
|
{
|
||||||
|
if !self.is_valid() {
|
||||||
|
return U::default();
|
||||||
|
}
|
||||||
|
|
||||||
let p_samples = Point3f::new(
|
let p_samples = Point3f::new(
|
||||||
p.x() * self.nx as Float - 0.5,
|
p.x() * self.nx as Float - 0.5,
|
||||||
p.y() * self.ny as Float - 0.5,
|
p.y() * self.ny as Float - 0.5,
|
||||||
|
|
@ -298,6 +320,10 @@ impl<T> SampledGrid<T> {
|
||||||
F: Fn(&T) -> U,
|
F: Fn(&T) -> U,
|
||||||
U: PartialOrd + Default,
|
U: PartialOrd + Default,
|
||||||
{
|
{
|
||||||
|
if !self.is_valid() {
|
||||||
|
return U::default();
|
||||||
|
}
|
||||||
|
|
||||||
let ps = [
|
let ps = [
|
||||||
Point3f::new(
|
Point3f::new(
|
||||||
bounds.p_min.x() * self.nx as Float - 0.5,
|
bounds.p_min.x() * self.nx as Float - 0.5,
|
||||||
|
|
@ -318,7 +344,6 @@ impl<T> SampledGrid<T> {
|
||||||
self.nz - 1,
|
self.nz - 1,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Initialize with the first voxel
|
|
||||||
let mut max_value = self.lookup_int_convert(pi_min, &convert);
|
let mut max_value = self.lookup_int_convert(pi_min, &convert);
|
||||||
|
|
||||||
for z in pi_min.z()..=pi_max.z() {
|
for z in pi_min.z()..=pi_max.z() {
|
||||||
|
|
@ -342,30 +367,3 @@ impl<T> SampledGrid<T> {
|
||||||
self.max_value_convert(bounds, |v| v.clone())
|
self.max_value_convert(bounds, |v| v.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InternCache<T> {
|
|
||||||
cache: Mutex<HashSet<Arc<T>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> InternCache<T>
|
|
||||||
where
|
|
||||||
T: Eq + Hash + Clone,
|
|
||||||
{
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
cache: Mutex::new(HashSet::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lookup(&self, value: T) -> Arc<T> {
|
|
||||||
let mut lock = self.cache.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(existing) = lock.get(&value) {
|
|
||||||
return existing.clone(); // Returns a cheap Arc copy
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_item = Arc::new(value);
|
|
||||||
lock.insert(new_item.clone());
|
|
||||||
new_item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use crate::utils::hash::{hash_buffer, mix_bits};
|
||||||
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
|
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
|
||||||
|
|
||||||
use num_traits::{Float as NumFloat, Num, One, Signed, Zero};
|
use num_traits::{Float as NumFloat, Num, One, Signed, Zero};
|
||||||
use rayon::prelude::*;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::iter::{Product, Sum};
|
use std::iter::{Product, Sum};
|
||||||
|
|
@ -64,16 +63,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
|
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
|
||||||
if coeffs.is_empty() {
|
assert!(!coeffs.is_empty());
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = coeffs[0];
|
let mut result = coeffs[0];
|
||||||
for &c in &coeffs[1..] {
|
for &c in &coeffs[1..] {
|
||||||
result = t.mul_add(result, c);
|
result = t.mul_add(result, c);
|
||||||
}
|
}
|
||||||
Some(result)
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -913,6 +909,7 @@ pub fn multiply_generator(c: &[u32], mut a: u32) -> u32 {
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct NoRandomizer;
|
pub struct NoRandomizer;
|
||||||
impl NoRandomizer {
|
impl NoRandomizer {
|
||||||
|
|
@ -921,6 +918,7 @@ impl NoRandomizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct BinaryPermuteScrambler {
|
pub struct BinaryPermuteScrambler {
|
||||||
pub permutation: u32,
|
pub permutation: u32,
|
||||||
|
|
@ -937,6 +935,7 @@ impl BinaryPermuteScrambler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct FastOwenScrambler {
|
pub struct FastOwenScrambler {
|
||||||
pub seed: u32,
|
pub seed: u32,
|
||||||
|
|
@ -970,6 +969,7 @@ impl FastOwenScrambler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct OwenScrambler {
|
pub struct OwenScrambler {
|
||||||
pub seed: u32,
|
pub seed: u32,
|
||||||
|
|
@ -1096,9 +1096,10 @@ pub fn sobol_interval_to_index(m: u32, frame: u64, p: Point2i) -> u64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MATRIX STUFF (TEST THOROUGHLY)
|
// MATRIX STUFF (TEST THOROUGHLY)
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Matrix<T, const R: usize, const C: usize> {
|
pub struct Matrix<T, const R: usize, const C: usize> {
|
||||||
m: [[T; C]; R],
|
pub m: [[T; C]; R],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, const R: usize, const C: usize> Matrix<T, R, C> {
|
impl<T, const R: usize, const C: usize> Matrix<T, R, C> {
|
||||||
|
|
@ -1261,6 +1262,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SquareMatrix<T, const N: usize> = Matrix<T, N, N>;
|
pub type SquareMatrix<T, const N: usize> = Matrix<T, N, N>;
|
||||||
|
pub type SquareMatrix3f = SquareMatrix<Float, 3>;
|
||||||
|
|
||||||
impl<T, const N: usize> SquareMatrix<T, N> {
|
impl<T, const N: usize> SquareMatrix<T, N> {
|
||||||
pub fn identity() -> Self
|
pub fn identity() -> Self
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub mod interval;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub mod noise;
|
pub mod noise;
|
||||||
|
pub mod ptr;
|
||||||
pub mod quaternion;
|
pub mod quaternion;
|
||||||
pub mod rng;
|
pub mod rng;
|
||||||
pub mod sampling;
|
pub mod sampling;
|
||||||
|
|
@ -14,6 +15,7 @@ pub mod sobol;
|
||||||
pub mod splines;
|
pub mod splines;
|
||||||
pub mod transform;
|
pub mod transform;
|
||||||
|
|
||||||
|
pub use ptr::Ptr;
|
||||||
pub use transform::{AnimatedTransform, Transform, TransformGeneric};
|
pub use transform::{AnimatedTransform, Transform, TransformGeneric};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
49
shared/src/utils/ptr.rs
Normal file
49
shared/src/utils/ptr.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Ptr<T: ?Sized> {
|
||||||
|
offset: isize,
|
||||||
|
_phantom: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Clone for Ptr<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Copy for Ptr<T> {}
|
||||||
|
|
||||||
|
impl<T> Ptr<T> {
|
||||||
|
pub fn null() -> Self {
|
||||||
|
Self {
|
||||||
|
offset: 0,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_null(&self) -> Self {
|
||||||
|
self.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> Option<&T> {
|
||||||
|
if self.offset == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
let base = self as *const _ as *const u8;
|
||||||
|
let target = base.offset(self.offset) as *const T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "cuda"))]
|
||||||
|
pub fn new(me: *const Self, target: *const T) -> Self {
|
||||||
|
let diff = unsafe { (target as *const u8).offset_from(me as *const u8) };
|
||||||
|
Self {
|
||||||
|
offset: diff,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::f32::consts::PI;
|
|
||||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::core::geometry::{Vector3f, VectorLike};
|
use crate::core::geometry::{Vector3f, VectorLike};
|
||||||
use crate::core::pbrt::Float;
|
|
||||||
use crate::utils::math::{safe_asin, sinx_over_x};
|
use crate::utils::math::{safe_asin, sinx_over_x};
|
||||||
|
use crate::{Float, PI};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct Quaternion {
|
pub struct Quaternion {
|
||||||
pub v: Vector3f,
|
pub v: Vector3f,
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ use std::iter::{Product, Sum};
|
||||||
use std::ops::{Add, Div, Index, IndexMut, Mul};
|
use std::ops::{Add, Div, Index, IndexMut, Mul};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::math::{radians, safe_acos, SquareMatrix};
|
use super::math::{SquareMatrix, radians, safe_acos};
|
||||||
use super::quaternion::Quaternion;
|
use super::quaternion::Quaternion;
|
||||||
|
use crate::core::color::{RGB, XYZ};
|
||||||
use crate::core::geometry::{
|
use crate::core::geometry::{
|
||||||
Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi,
|
Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi,
|
||||||
VectorLike,
|
VectorLike,
|
||||||
|
|
@ -14,10 +15,10 @@ use crate::core::geometry::{
|
||||||
use crate::core::interaction::{
|
use crate::core::interaction::{
|
||||||
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||||
};
|
};
|
||||||
use crate::core::pbrt::{gamma, Float};
|
use crate::core::pbrt::{Float, gamma};
|
||||||
use crate::spectra::{RGB, XYZ};
|
|
||||||
use crate::utils::error::InversionError;
|
use crate::utils::error::InversionError;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct TransformGeneric<T: NumFloat> {
|
pub struct TransformGeneric<T: NumFloat> {
|
||||||
m: SquareMatrix<T, 4>,
|
m: SquareMatrix<T, 4>,
|
||||||
|
|
@ -754,6 +755,7 @@ impl From<Quaternion> for TransformGeneric<Float> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Default, Debug, Copy, Clone)]
|
#[derive(Default, Debug, Copy, Clone)]
|
||||||
pub struct DerivativeTerm {
|
pub struct DerivativeTerm {
|
||||||
kc: Float,
|
kc: Float,
|
||||||
|
|
@ -777,7 +779,8 @@ impl DerivativeTerm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Default, Clone)]
|
||||||
pub struct AnimatedTransform {
|
pub struct AnimatedTransform {
|
||||||
pub start_transform: Transform,
|
pub start_transform: Transform,
|
||||||
pub end_transform: Transform,
|
pub end_transform: Transform,
|
||||||
|
|
|
||||||
413
src/core/camera.rs
Normal file
413
src/core/camera.rs
Normal file
|
|
@ -0,0 +1,413 @@
|
||||||
|
use crate::utils::{FileLoc, ParameterDictionary};
|
||||||
|
use shared::Float;
|
||||||
|
use shared::cameras::*;
|
||||||
|
use shared::core::camera::{Camera, CameraBase, CameraTransform};
|
||||||
|
use shared::core::film::Film;
|
||||||
|
use shared::core::medium::Medium;
|
||||||
|
use shared::core::options::get_options;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct CameraBaseParameters {
|
||||||
|
pub camera_transform: CameraTransform,
|
||||||
|
pub shutter_open: Float,
|
||||||
|
pub shutter_close: Float,
|
||||||
|
pub film: Arc<Film>,
|
||||||
|
pub medium: Arc<Medium>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraBaseParameters {
|
||||||
|
pub fn new(
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
film: Arc<Film>,
|
||||||
|
medium: Arc<Medium>,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Self {
|
||||||
|
let mut shutter_open = params.get_one_float("shutteropen", 0.);
|
||||||
|
let mut shutter_close = params.get_one_float("shutterclose", 1.);
|
||||||
|
if shutter_close < shutter_open {
|
||||||
|
eprint!(
|
||||||
|
"{}: Shutter close time {} < shutter open {}. Swapping",
|
||||||
|
loc, shutter_close, shutter_open
|
||||||
|
);
|
||||||
|
std::mem::swap(&mut shutter_open, &mut shutter_close);
|
||||||
|
}
|
||||||
|
CameraBaseParameters {
|
||||||
|
camera_transform: camera_transform.clone(),
|
||||||
|
shutter_open,
|
||||||
|
shutter_close,
|
||||||
|
film,
|
||||||
|
medium,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CameraBaseFactory {
|
||||||
|
fn create(p: CameraBaseParameters) -> CameraBase {
|
||||||
|
CameraBase {
|
||||||
|
camera_transform: p.camera_transform.as_ref().clone(),
|
||||||
|
shutter_open: p.shutter_open,
|
||||||
|
shutter_close: p.shutter_close,
|
||||||
|
film: p.film.clone(),
|
||||||
|
medium: p.medium.clone(),
|
||||||
|
min_pos_differential_x: Vector3f::default(),
|
||||||
|
min_pos_differential_y: Vector3f::default(),
|
||||||
|
min_dir_differential_x: Vector3f::default(),
|
||||||
|
min_dir_differential_y: Vector3f::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraBaseFactory for CameraBase {}
|
||||||
|
|
||||||
|
pub trait CameraFactory {
|
||||||
|
fn create(
|
||||||
|
name: &str,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
medium: Medium,
|
||||||
|
film: Arc<Film>,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraFactory for Camera {
|
||||||
|
fn create(
|
||||||
|
name: &str,
|
||||||
|
params: &ParameterDictionary,
|
||||||
|
camera_transform: &CameraTransform,
|
||||||
|
medium: Medium,
|
||||||
|
film: Arc<Film>,
|
||||||
|
loc: &FileLoc,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
match name {
|
||||||
|
"perspective" => {
|
||||||
|
let full_res = film.full_resolution();
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let lens_radius = params.get_one_float("lensradius", 0.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 1e6);
|
||||||
|
let frame = params.get_one_float(
|
||||||
|
"frameaspectratio",
|
||||||
|
full_res.x() as Float / full_res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut screen = if frame > 1. {
|
||||||
|
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(
|
||||||
|
Point2f::new(-1., -1. / frame),
|
||||||
|
Point2f::new(1., 1. / frame),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let sw = params.get_float_array("screenwindow");
|
||||||
|
if !sw.is_empty() {
|
||||||
|
if get_options().fullscreen {
|
||||||
|
eprint!("Screenwindow is ignored in fullscreen mode");
|
||||||
|
} else {
|
||||||
|
if sw.len() == 4 {
|
||||||
|
screen = Bounds2f::from_points(
|
||||||
|
Point2f::new(sw[0], sw[2]),
|
||||||
|
Point2f::new(sw[1], sw[3]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: screenwindow param must have four values",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let fov = params.get_one_float("fov", 90.);
|
||||||
|
let camera = PerspectiveCamera::new(base, fov, screen, lens_radius, focal_distance);
|
||||||
|
|
||||||
|
Ok(Camera::Perspective(camera))
|
||||||
|
}
|
||||||
|
|
||||||
|
"orthographic" => {
|
||||||
|
let full_res = film.full_resolution();
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let lens_radius = params.get_one_float("lensradius", 0.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 1e6);
|
||||||
|
let frame = params.get_one_float(
|
||||||
|
"frameaspectratio",
|
||||||
|
full_res.x() as Float / full_res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut screen = if frame > 1. {
|
||||||
|
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(
|
||||||
|
Point2f::new(-1., -1. / frame),
|
||||||
|
Point2f::new(1., 1. / frame),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let sw = params.get_float_array("screenwindow");
|
||||||
|
if !sw.is_empty() {
|
||||||
|
if get_options().fullscreen {
|
||||||
|
eprint!("Screenwindow is ignored in fullscreen mode");
|
||||||
|
} else {
|
||||||
|
if sw.len() == 4 {
|
||||||
|
screen = Bounds2f::from_points(
|
||||||
|
Point2f::new(sw[0], sw[2]),
|
||||||
|
Point2f::new(sw[1], sw[3]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: screenwindow param must have four values",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let camera = OrthographicCamera::new(base, screen, lens_radius, focal_distance);
|
||||||
|
Ok(Camera::Orthographic(camera))
|
||||||
|
}
|
||||||
|
"realistic" => {
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let aperture_diameter = params.get_one_float("aperturediameter", 1.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 10.);
|
||||||
|
let lens_file = params.get_one_string("lensfile", "");
|
||||||
|
|
||||||
|
if lens_file.is_empty() {
|
||||||
|
return Err(format!("{}: No lens file supplied", loc));
|
||||||
|
}
|
||||||
|
|
||||||
|
let lens_params = read_float_file(lens_file.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
if lens_params.len() % 4 != 0 {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: excess values in lens specification file; must be multiple-of-four values, read {}",
|
||||||
|
loc,
|
||||||
|
lens_params.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let builtin_res = 256;
|
||||||
|
let rasterize = |vert: &[Point2f]| -> Image {
|
||||||
|
let mut image = Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
Point2i::new(builtin_res, builtin_res),
|
||||||
|
&["Y"],
|
||||||
|
SRGB,
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = image.resolution();
|
||||||
|
for y in 0..res.y() {
|
||||||
|
for x in 0..res.x() {
|
||||||
|
// Map pixel to [-1, 1] range
|
||||||
|
let p = Point2f::new(
|
||||||
|
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
|
||||||
|
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut winding_number = 0;
|
||||||
|
// Winding number test against edges
|
||||||
|
for i in 0..vert.len() {
|
||||||
|
let i1 = (i + 1) % vert.len();
|
||||||
|
let v_i = vert[i];
|
||||||
|
let v_i1 = vert[i1];
|
||||||
|
|
||||||
|
let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y())
|
||||||
|
- (p.y() - v_i.y()) * (v_i1.x() - v_i.x());
|
||||||
|
|
||||||
|
if v_i.y() <= p.y() {
|
||||||
|
if v_i1.y() > p.y() && e > 0.0 {
|
||||||
|
winding_number += 1;
|
||||||
|
}
|
||||||
|
} else if v_i1.y() <= p.y() && e < 0.0 {
|
||||||
|
winding_number -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image.set_channel(
|
||||||
|
Point2i::new(x, y),
|
||||||
|
0,
|
||||||
|
if winding_number == 0 { 0.0 } else { 1.0 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image
|
||||||
|
};
|
||||||
|
|
||||||
|
let aperture_name = params.get_one_string("aperture", "");
|
||||||
|
let mut aperture_image: Option<Image> = None;
|
||||||
|
|
||||||
|
if !aperture_name.is_empty() {
|
||||||
|
match aperture_name.as_str() {
|
||||||
|
"gaussian" => {
|
||||||
|
let mut img = Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
Point2i::new(builtin_res, builtin_res),
|
||||||
|
&["Y"],
|
||||||
|
SRGB,
|
||||||
|
);
|
||||||
|
let res = img.resolution();
|
||||||
|
for y in 0..res.y() {
|
||||||
|
for x in 0..res.x() {
|
||||||
|
let uv = Point2f::new(
|
||||||
|
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
|
||||||
|
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
|
||||||
|
);
|
||||||
|
let r2 = square(uv.x()) + square(uv.y());
|
||||||
|
let sigma2 = 1.0;
|
||||||
|
let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0);
|
||||||
|
img.set_channel(Point2i::new(x, y), 0, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aperture_image = Some(img);
|
||||||
|
}
|
||||||
|
"square" => {
|
||||||
|
let mut img = Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
Point2i::new(builtin_res, builtin_res),
|
||||||
|
&["Y"],
|
||||||
|
SRGB,
|
||||||
|
);
|
||||||
|
let low = (0.25 * builtin_res as Float) as i32;
|
||||||
|
let high = (0.75 * builtin_res as Float) as i32;
|
||||||
|
for y in low..high {
|
||||||
|
for x in low..high {
|
||||||
|
img.set_channel(Point2i::new(x, y), 0, 4.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aperture_image = Some(img);
|
||||||
|
}
|
||||||
|
"pentagon" => {
|
||||||
|
let c1 = (5.0f32.sqrt() - 1.0) / 4.0;
|
||||||
|
let c2 = (5.0f32.sqrt() + 1.0) / 4.0;
|
||||||
|
let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
|
||||||
|
let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
|
||||||
|
let mut vert = [
|
||||||
|
Point2f::new(0.0, 1.0),
|
||||||
|
Point2f::new(s1, c1),
|
||||||
|
Point2f::new(s2, -c2),
|
||||||
|
Point2f::new(-s2, -c2),
|
||||||
|
Point2f::new(-s1, c1),
|
||||||
|
];
|
||||||
|
for v in vert.iter_mut() {
|
||||||
|
*v = Point2f::from(Vector2f::from(*v) * 0.8);
|
||||||
|
}
|
||||||
|
aperture_image = Some(rasterize(&vert));
|
||||||
|
}
|
||||||
|
"star" => {
|
||||||
|
let mut vert = Vec::with_capacity(10);
|
||||||
|
for i in 0..10 {
|
||||||
|
let r = if i % 2 == 1 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
(72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos())
|
||||||
|
};
|
||||||
|
let angle = PI * i as Float / 5.0;
|
||||||
|
vert.push(Point2f::new(r * angle.cos(), r * angle.sin()));
|
||||||
|
}
|
||||||
|
vert.reverse();
|
||||||
|
aperture_image = Some(rasterize(&vert));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Ok(im) = Image::read(Path::new(&aperture_name), None) {
|
||||||
|
if im.image.n_channels() > 1 {
|
||||||
|
let mut mono = Image::new(
|
||||||
|
PixelFormat::F32,
|
||||||
|
im.image.resolution(),
|
||||||
|
&["Y"],
|
||||||
|
SRGB,
|
||||||
|
);
|
||||||
|
let res = mono.resolution();
|
||||||
|
for y in 0..res.y() {
|
||||||
|
for x in 0..res.x() {
|
||||||
|
let avg = im
|
||||||
|
.image
|
||||||
|
.get_channels_default(Point2i::new(x, y))
|
||||||
|
.average();
|
||||||
|
mono.set_channel(Point2i::new(x, y), 0, avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aperture_image = Some(mono);
|
||||||
|
} else {
|
||||||
|
aperture_image = Some(im.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let camera = RealisticCamera::new(
|
||||||
|
base,
|
||||||
|
lens_params,
|
||||||
|
focal_distance,
|
||||||
|
aperture_diameter,
|
||||||
|
aperture_image,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Camera::Realistic(camera))
|
||||||
|
}
|
||||||
|
"spherical" => {
|
||||||
|
let full_res = film.full_resolution();
|
||||||
|
let camera_params =
|
||||||
|
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
|
||||||
|
let base = CameraBase::new(camera_params);
|
||||||
|
let lens_radius = params.get_one_float("lensradius", 0.);
|
||||||
|
let focal_distance = params.get_one_float("focaldistance", 1e30);
|
||||||
|
let frame = params.get_one_float(
|
||||||
|
"frameaspectratio",
|
||||||
|
full_res.x() as Float / full_res.y() as Float,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut screen = if frame > 1. {
|
||||||
|
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
|
||||||
|
} else {
|
||||||
|
Bounds2f::from_points(
|
||||||
|
Point2f::new(-1., -1. / frame),
|
||||||
|
Point2f::new(1., 1. / frame),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let sw = params.get_float_array("screenwindow");
|
||||||
|
if !sw.is_empty() {
|
||||||
|
if get_options().fullscreen {
|
||||||
|
eprint!("Screenwindow is ignored in fullscreen mode");
|
||||||
|
} else {
|
||||||
|
if sw.len() == 4 {
|
||||||
|
screen = Bounds2f::from_points(
|
||||||
|
Point2f::new(sw[0], sw[2]),
|
||||||
|
Point2f::new(sw[1], sw[3]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: screenwindow param must have four values",
|
||||||
|
loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = params.get_one_string("mapping", "equalarea");
|
||||||
|
let mapping = match m.as_str() {
|
||||||
|
"equalarea" => Mapping::EqualArea,
|
||||||
|
"equirectangular" => Mapping::EquiRectangular,
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: unknown mapping for spherical camera at {}",
|
||||||
|
m, loc
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let camera = SphericalCamera { mapping, base };
|
||||||
|
|
||||||
|
Ok(Camera::Spherical(camera))
|
||||||
|
}
|
||||||
|
_ => Err(format!("Camera type '{}' unknown at {}", name, loc)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/core/color.rs
Normal file
47
src/core/color.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use shared::Float;
|
||||||
|
use shared::core::color::{Coeffs, RES, RGBToSpectrumTable};
|
||||||
|
use shared::spectra::RGBSigmoidPolynomial;
|
||||||
|
|
||||||
|
pub struct RGBToSpectrumTableData {
|
||||||
|
_z_nodes: Vec<Float>,
|
||||||
|
_coeffs: Vec<Float>,
|
||||||
|
|
||||||
|
pub view: RGBToSpectrumTable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for RGBToSpectrumTableData {
|
||||||
|
type Target = RGBToSpectrumTable;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RGBToSpectrumTableData {
|
||||||
|
pub fn new(z_nodes: Vec<Float>, coeffs: Vec<Float>) -> Self {
|
||||||
|
assert_eq!(z_nodes.len(), RES);
|
||||||
|
assert_eq!(coeffs.len(), RES * RES * RES * 3 * 3); // bucket*z*y*x*3(coeffs)
|
||||||
|
|
||||||
|
let view = RGBToSpectrumTable {
|
||||||
|
z_nodes: z_nodes.as_ptr(),
|
||||||
|
coeffs: coeffs.as_ptr() as *const Coeffs,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
_z_nodes: z_nodes,
|
||||||
|
_coeffs: coeffs,
|
||||||
|
view,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LMS_FROM_XYZ: SquareMatrix3f = SquareMatrix::new([
|
||||||
|
[0.8951, 0.2664, -0.1614],
|
||||||
|
[-0.7502, 1.7135, 0.0367],
|
||||||
|
[0.0389, -0.0685, 1.0296],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const XYZ_FROM_LMS: SquareMatrix3f = SquareMatrix::new([
|
||||||
|
[0.986993, -0.147054, 0.159963],
|
||||||
|
[0.432305, 0.51836, 0.0492912],
|
||||||
|
[-0.00852866, 0.0400428, 0.968487],
|
||||||
|
]);
|
||||||
|
|
@ -88,7 +88,7 @@ impl FilmBaseHost for FilmBase {
|
||||||
fn create(
|
fn create(
|
||||||
params: &ParameterDictionary,
|
params: &ParameterDictionary,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
sensor: Option<PixelSensor>,
|
sensor: Option<&PixelSensor>,
|
||||||
loc: &FileLoc,
|
loc: &FileLoc,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let x_res = params.get_one_int("xresolution", 1280);
|
let x_res = params.get_one_int("xresolution", 1280);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
pub mod camera;
|
||||||
|
pub mod color;
|
||||||
pub mod film;
|
pub mod film;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
|
pub mod spectrum;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
|
|
|
||||||
8
src/core/spectrum.rs
Normal file
8
src/core/spectrum.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
use crate::utils::containers::InternCache;
|
||||||
|
|
||||||
|
static SPECTRUM_CACHE: Lazy<Mutex<HashMap<String, Spectrum>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
|
||||||
|
SPECTRUM_CACHE.get_or_init(InternCache::new)
|
||||||
|
}
|
||||||
|
|
@ -250,41 +250,3 @@ struct TexInfo {
|
||||||
wrap_mode: WrapMode,
|
wrap_mode: WrapMode,
|
||||||
encoding: ColorEncoding,
|
encoding: ColorEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TextureEvaluator: Send + Sync {
|
|
||||||
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float;
|
|
||||||
fn evaluate_spectrum(
|
|
||||||
&self,
|
|
||||||
tex: &SpectrumTexture,
|
|
||||||
ctx: &TextureEvalContext,
|
|
||||||
lambda: &SampledWavelengths,
|
|
||||||
) -> SampledSpectrum;
|
|
||||||
|
|
||||||
fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default)]
|
|
||||||
pub struct UniversalTextureEvaluator;
|
|
||||||
|
|
||||||
impl TextureEvaluator for UniversalTextureEvaluator {
|
|
||||||
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float {
|
|
||||||
tex.evaluate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate_spectrum(
|
|
||||||
&self,
|
|
||||||
tex: &SpectrumTexture,
|
|
||||||
ctx: &TextureEvalContext,
|
|
||||||
lambda: &SampledWavelengths,
|
|
||||||
) -> SampledSpectrum {
|
|
||||||
tex.evaluate(ctx, lambda)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_evaluate(
|
|
||||||
&self,
|
|
||||||
_float_textures: &[&FloatTexture],
|
|
||||||
_spectrum_textures: &[&SpectrumTexture],
|
|
||||||
) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod lights;
|
pub mod lights;
|
||||||
|
pub mod spectra;
|
||||||
pub mod textures;
|
pub mod textures;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
|
||||||
58
src/spectra/colorspace.rs
Normal file
58
src/spectra/colorspace.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use super::DenselySampledSpectrumBuffer;
|
||||||
|
use shared::core::geometry::Point2f;
|
||||||
|
use shared::spectra::{RGBColorSpace, RGBToSpectrumTable};
|
||||||
|
use shared::utils::SquareMatrix;
|
||||||
|
|
||||||
|
pub struct RGBColorSpaceData {
|
||||||
|
_illuminant: DenselySampledBuffer,
|
||||||
|
pub view: RGBColorSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
r: Point2f,
|
||||||
|
g: Point2f,
|
||||||
|
b: Point2f,
|
||||||
|
illuminant: DenselySampledBuffer,
|
||||||
|
rgb_to_spectrum_table: *const RGBToSpectrumTable,
|
||||||
|
) -> Self {
|
||||||
|
let w_xyz: XYZ = illuminant.to_xyz();
|
||||||
|
let w = w_xyz.xy();
|
||||||
|
let r_xyz = XYZ::from_xyy(r, Some(1.0));
|
||||||
|
let g_xyz = XYZ::from_xyy(g, Some(1.0));
|
||||||
|
let b_xyz = XYZ::from_xyy(b, Some(1.0));
|
||||||
|
let rgb_values = [
|
||||||
|
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
|
||||||
|
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
|
||||||
|
[r_xyz.z(), g_xyz.z(), b_xyz.z()],
|
||||||
|
];
|
||||||
|
let rgb = SquareMatrix::new(rgb_values);
|
||||||
|
let c: RGB = rgb.inverse()? * w_xyz;
|
||||||
|
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
|
||||||
|
let rgb_from_xyz = xyz_from_rgb
|
||||||
|
.inverse()
|
||||||
|
.expect("XYZ from RGB matrix is singular");
|
||||||
|
let view = RGBColorSpace {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
w,
|
||||||
|
illuminant: illuminant_buffer.view, // Extract view
|
||||||
|
xyz_from_rgb,
|
||||||
|
rgb_from_xyz,
|
||||||
|
rgb_to_spectrum_table: table_ptr,
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
_illuminant: illuminant_buffer,
|
||||||
|
view,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_named(name: &str) -> Result<Arc<RGBColorSpace>, String> {
|
||||||
|
match name.to_lowercase().as_str() {
|
||||||
|
"aces2065-1" => Ok(Self::aces2065_1().clone()),
|
||||||
|
"rec2020" => Ok(Self::rec2020().clone()),
|
||||||
|
"dci-p3" => Ok(Self::dci_p3().clone()),
|
||||||
|
"srgb" => Ok(Self::srgb().clone()),
|
||||||
|
_ => Err(format!("Color space '{}' not found", name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
162
src/spectra/data.rs
Normal file
162
src/spectra/data.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
use crate::spectra::{DenselySampledSpectrumBuffer, SpectrumData};
|
||||||
|
use shared::Float;
|
||||||
|
use shared::core::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, CIE_X, CIE_Y, CIE_Z, N_CIES};
|
||||||
|
use shared::spectra::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, N_CIES};
|
||||||
|
use shared::spectra::{
|
||||||
|
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, PiecewiseLinearSpectrum,
|
||||||
|
Spectrum,
|
||||||
|
};
|
||||||
|
use shared::utils::math::square;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
fn create_cie_buffer(data: &[Float]) -> Spectrum {
|
||||||
|
let buffer = PiecewiseLinearSpectrum::from_interleaved(data, false);
|
||||||
|
let spec = Spectrum::Piecewise(buffer.view);
|
||||||
|
DenselySampledSpectrumBuffer::from_spectrum(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
|
||||||
|
macro_rules! add {
|
||||||
|
($name:expr, $data:expr, $norm:expr) => {
|
||||||
|
let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved($data, $norm);
|
||||||
|
m.insert($name.to_string(), buffer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
add!("stdillum-A", &CIE_ILLUM_A, true);
|
||||||
|
add!("stdillum-D50", &CIE_ILLUM_D5000, true);
|
||||||
|
add!("stdillum-D65", &CIE_ILLUM_D6500, true);
|
||||||
|
add!("stdillum-F1", &CIE_ILLUM_F1, true);
|
||||||
|
add!("stdillum-F2", &CIE_ILLUM_F2, true);
|
||||||
|
add!("stdillum-F3", &CIE_ILLUM_F3, true);
|
||||||
|
add!("stdillum-F4", &CIE_ILLUM_F4, true);
|
||||||
|
add!("stdillum-F5", &CIE_ILLUM_F5, true);
|
||||||
|
add!("stdillum-F6", &CIE_ILLUM_F6, true);
|
||||||
|
add!("stdillum-F7", &CIE_ILLUM_F7, true);
|
||||||
|
add!("stdillum-F8", &CIE_ILLUM_F8, true);
|
||||||
|
add!("stdillum-F9", &CIE_ILLUM_F9, true);
|
||||||
|
add!("stdillum-F10", &CIE_ILLUM_F10, true);
|
||||||
|
add!("stdillum-F11", &CIE_ILLUM_F11, true);
|
||||||
|
add!("stdillum-F12", &CIE_ILLUM_F12, true);
|
||||||
|
add!("illum-acesD60", &ACES_ILLUM_D60, true);
|
||||||
|
|
||||||
|
// --- Glasses ---
|
||||||
|
add!("glass-BK7", &GLASS_BK7_ETA, false);
|
||||||
|
add!("glass-BAF10", &GLASS_BAF10_ETA, false);
|
||||||
|
add!("glass-FK51A", &GLASS_FK51A_ETA, false);
|
||||||
|
add!("glass-LASF9", &GLASS_LASF9_ETA, false);
|
||||||
|
add!("glass-F5", &GLASS_SF5_ETA, false);
|
||||||
|
add!("glass-F10", &GLASS_SF10_ETA, false);
|
||||||
|
add!("glass-F11", &GLASS_SF11_ETA, false);
|
||||||
|
|
||||||
|
// --- Metals ---
|
||||||
|
add!("metal-Ag-eta", &AG_ETA, false);
|
||||||
|
add!("metal-Ag-k", &AG_K, false);
|
||||||
|
add!("metal-Al-eta", &AL_ETA, false);
|
||||||
|
add!("metal-Al-k", &AL_K, false);
|
||||||
|
add!("metal-Au-eta", &AU_ETA, false);
|
||||||
|
add!("metal-Au-k", &AU_K, false);
|
||||||
|
add!("metal-Cu-eta", &CU_ETA, false);
|
||||||
|
add!("metal-Cu-k", &CU_K, false);
|
||||||
|
add!("metal-CuZn-eta", &CUZN_ETA, false);
|
||||||
|
add!("metal-CuZn-k", &CUZN_K, false);
|
||||||
|
add!("metal-MgO-eta", &MGO_ETA, false);
|
||||||
|
add!("metal-MgO-k", &MGO_K, false);
|
||||||
|
add!("metal-TiO2-eta", &TIO2_ETA, false);
|
||||||
|
add!("metal-TiO2-k", &TIO2_K, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 100D ---
|
||||||
|
add!("canon_eos_100d_r", &CANON_EOS_100D_R, false);
|
||||||
|
add!("canon_eos_100d_g", &CANON_EOS_100D_G, false);
|
||||||
|
add!("canon_eos_100d_b", &CANON_EOS_100D_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 1DX MkII ---
|
||||||
|
add!("canon_eos_1dx_mkii_r", &CANON_EOS_1DX_MKII_R, false);
|
||||||
|
add!("canon_eos_1dx_mkii_g", &CANON_EOS_1DX_MKII_G, false);
|
||||||
|
add!("canon_eos_1dx_mkii_b", &CANON_EOS_1DX_MKII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 200D ---
|
||||||
|
add!("canon_eos_200d_r", &CANON_EOS_200D_R, false);
|
||||||
|
add!("canon_eos_200d_g", &CANON_EOS_200D_G, false);
|
||||||
|
add!("canon_eos_200d_b", &CANON_EOS_200D_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 200D MkII ---
|
||||||
|
add!("canon_eos_200d_mkii_r", &CANON_EOS_200D_MKII_R, false);
|
||||||
|
add!("canon_eos_200d_mkii_g", &CANON_EOS_200D_MKII_G, false);
|
||||||
|
add!("canon_eos_200d_mkii_b", &CANON_EOS_200D_MKII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D ---
|
||||||
|
add!("canon_eos_5d_r", &CANON_EOS_5D_R, false);
|
||||||
|
add!("canon_eos_5d_g", &CANON_EOS_5D_G, false);
|
||||||
|
add!("canon_eos_5d_b", &CANON_EOS_5D_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D MkII ---
|
||||||
|
add!("canon_eos_5d_mkii_r", &CANON_EOS_5D_MKII_R, false);
|
||||||
|
add!("canon_eos_5d_mkii_g", &CANON_EOS_5D_MKII_G, false);
|
||||||
|
add!("canon_eos_5d_mkii_b", &CANON_EOS_5D_MKII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D MkIII ---
|
||||||
|
add!("canon_eos_5d_mkiii_r", &CANON_EOS_5D_MKIII_R, false);
|
||||||
|
add!("canon_eos_5d_mkiii_g", &CANON_EOS_5D_MKIII_G, false);
|
||||||
|
add!("canon_eos_5d_mkiii_b", &CANON_EOS_5D_MKIII_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5D MkIV ---
|
||||||
|
add!("canon_eos_5d_mkiv_r", &CANON_EOS_5D_MKIV_R, false);
|
||||||
|
add!("canon_eos_5d_mkiv_g", &CANON_EOS_5D_MKIV_G, false);
|
||||||
|
add!("canon_eos_5d_mkiv_b", &CANON_EOS_5D_MKIV_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS 5DS ---
|
||||||
|
add!("canon_eos_5ds_r", &CANON_EOS_5DS_R, false);
|
||||||
|
add!("canon_eos_5ds_g", &CANON_EOS_5DS_G, false);
|
||||||
|
add!("canon_eos_5ds_b", &CANON_EOS_5DS_B, false);
|
||||||
|
|
||||||
|
// --- Canon EOS M ---
|
||||||
|
add!("canon_eos_m_r", &CANON_EOS_M_R, false);
|
||||||
|
add!("canon_eos_m_g", &CANON_EOS_M_G, false);
|
||||||
|
add!("canon_eos_m_b", &CANON_EOS_M_B, false);
|
||||||
|
|
||||||
|
// --- Hasselblad L1D 20C ---
|
||||||
|
add!("hasselblad_l1d_20c_r", &HASSELBLAD_L1D_20C_R, false);
|
||||||
|
add!("hasselblad_l1d_20c_g", &HASSELBLAD_L1D_20C_G, false);
|
||||||
|
add!("hasselblad_l1d_20c_b", &HASSELBLAD_L1D_20C_B, false);
|
||||||
|
|
||||||
|
// --- Nikon D810 ---
|
||||||
|
add!("nikon_d810_r", &NIKON_D810_R, false);
|
||||||
|
add!("nikon_d810_g", &NIKON_D810_G, false);
|
||||||
|
add!("nikon_d810_b", &NIKON_D810_B, false);
|
||||||
|
|
||||||
|
// --- Nikon D850 ---
|
||||||
|
add!("nikon_d850_r", &NIKON_D850_R, false);
|
||||||
|
add!("nikon_d850_g", &NIKON_D850_G, false);
|
||||||
|
add!("nikon_d850_b", &NIKON_D850_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 6400 ---
|
||||||
|
add!("sony_ilce_6400_r", &SONY_ILCE_6400_R, false);
|
||||||
|
add!("sony_ilce_6400_g", &SONY_ILCE_6400_G, false);
|
||||||
|
add!("sony_ilce_6400_b", &SONY_ILCE_6400_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 7M3 ---
|
||||||
|
add!("sony_ilce_7m3_r", &SONY_ILCE_7M3_R, false);
|
||||||
|
add!("sony_ilce_7m3_g", &SONY_ILCE_7M3_G, false);
|
||||||
|
add!("sony_ilce_7m3_b", &SONY_ILCE_7M3_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 7RM3 ---
|
||||||
|
add!("sony_ilce_7rm3_r", &SONY_ILCE_7RM3_R, false);
|
||||||
|
add!("sony_ilce_7rm3_g", &SONY_ILCE_7RM3_G, false);
|
||||||
|
add!("sony_ilce_7rm3_b", &SONY_ILCE_7RM3_B, false);
|
||||||
|
|
||||||
|
// --- Sony ILCE 9 ---
|
||||||
|
add!("sony_ilce_9_r", &SONY_ILCE_9_R, false);
|
||||||
|
add!("sony_ilce_9_g", &SONY_ILCE_9_G, false);
|
||||||
|
add!("sony_ilce_9_b", &SONY_ILCE_9_B, false);
|
||||||
|
|
||||||
|
m
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn get_named_spectrum(name: &str) -> Option<Spectrum> {
|
||||||
|
let buffer = NAMED_SPECTRA_DATA.get(name)?;
|
||||||
|
Some(Spectrum::PiecewiseLinear(buffer.view))
|
||||||
|
}
|
||||||
112
src/spectra/dense.rs
Normal file
112
src/spectra/dense.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use shared::spectra::{DenselySampledSpectrum, SampledSpectrum};
|
||||||
|
|
||||||
|
pub struct DenselySampledSpectrumBuffer {
|
||||||
|
pub view: DenselySampledSpectrum,
|
||||||
|
_storage: Vec<Float>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for DenselySampledSpectrumBuffer {
|
||||||
|
type Target = DenselySampledSpectrum;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DenselySampledSpectrumBuffer {
|
||||||
|
pub fn new(lambda_min: i32, lambda_max: i32, values: Vec<Float>) -> Self {
|
||||||
|
let view = DenselySampledSpectrum {
|
||||||
|
lambda_min,
|
||||||
|
lambda_max,
|
||||||
|
values: values.as_ptr(),
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
view,
|
||||||
|
_storage: values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
self.sample(lambda)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_zero(lambda_min: i32, lambda_max: i32) -> Self {
|
||||||
|
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
|
||||||
|
let values = vec![0.0; n_values];
|
||||||
|
Self::new(lambda_min, lambda_max, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_spectrum(spec: &Spectrum) -> Self {
|
||||||
|
let lambda_min = LAMBDA_MIN;
|
||||||
|
let lambda_max = LAMBDA_MAX;
|
||||||
|
|
||||||
|
let mut values = Vec::with_capacity((lambda_max - lambda_min + 1) as usize);
|
||||||
|
|
||||||
|
for lambda in lambda_min..=lambda_max {
|
||||||
|
values.push(spec.evaluate(lambda as Float));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::new(lambda_min, lambda_max, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Float) -> Float,
|
||||||
|
{
|
||||||
|
let mut values = Vec::with_capacity((lambda_max - lambda_min + 1) as usize);
|
||||||
|
|
||||||
|
for lambda in lambda_min..=lambda_max {
|
||||||
|
values.push(f(lambda as Float));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::new(lambda_min, lambda_max, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_cie_d(temperature: Float) -> Self {
|
||||||
|
let cct = temperature * 1.4388 / 1.4380;
|
||||||
|
|
||||||
|
if cct < 4000.0 {
|
||||||
|
return Self::from_function(
|
||||||
|
|lambda| BlackbodySpectrum::new(cct).evaluate(lambda),
|
||||||
|
LAMBDA_MIN,
|
||||||
|
LAMBDA_MAX,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = if cct < 7000. {
|
||||||
|
-4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063
|
||||||
|
} else {
|
||||||
|
-2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704
|
||||||
|
};
|
||||||
|
let y = -3. * x + 2.87 * x - 0.275;
|
||||||
|
let m = 0.0241 + 0.2562 * x - 0.7341 * y;
|
||||||
|
let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m;
|
||||||
|
let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m;
|
||||||
|
|
||||||
|
let coarse_values: Vec<Float> = (0..N_CIES)
|
||||||
|
.map(|i| (CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let temp_pls = PiecewiseLinearSpectrum {
|
||||||
|
lambdas: CIE_S_LAMBDA.as_ptr(),
|
||||||
|
values: coarse_values.as_ptr(),
|
||||||
|
count: N_CIES as u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::from_function(|lambda| temp_pls.evaluate(lambda), LAMBDA_MIN, LAMBDA_MAX)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_view(&self) -> DenselySampledSpectrum {
|
||||||
|
DenselySampledSpectrum {
|
||||||
|
lambda_min: self.lambda_min,
|
||||||
|
lambda_max: self.lambda_max,
|
||||||
|
values: self.samples.as_ptr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale(&mut self, s: Float) {
|
||||||
|
for v in &mut self._storage {
|
||||||
|
*v *= s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/spectra/mod.rs
Normal file
109
src/spectra/mod.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use shared::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z};
|
||||||
|
use shared::core::spectrum::Spectrum;
|
||||||
|
use shared::spectra::RGBToSpectrumTable;
|
||||||
|
use std::sync::Lazy;
|
||||||
|
|
||||||
|
pub mod colorspace;
|
||||||
|
pub mod data;
|
||||||
|
pub mod dense;
|
||||||
|
pub mod piecewise;
|
||||||
|
|
||||||
|
use data;
|
||||||
|
pub use dense::DenselySampledSpectrumBuffer;
|
||||||
|
|
||||||
|
static CIE_X_DATA: Lazy<DenselySampledBuffer> = Lazy::new(|| data::create_cie_buffer(&CIE_X));
|
||||||
|
static CIE_Y_DATA: Lazy<DenselySampledBuffer> = Lazy::new(|| data::create_cie_buffer(&CIE_Y));
|
||||||
|
static CIE_Z_DATA: Lazy<DenselySampledBuffer> = Lazy::new(|| data::create_cie_buffer(&CIE_Z));
|
||||||
|
static CIE_D65_DATA: Lazy<DenselySampledBuffer> = Lazy::new(|| data::create_cie_buffer(&CIE_D65));
|
||||||
|
|
||||||
|
pub fn cie_x() -> Spectrum {
|
||||||
|
Spectrum::DenselySampled(CIE_X_DATA.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cie_y() -> Spectrum {
|
||||||
|
Spectrum::DenselySampled(CIE_Y_DATA.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cie_z() -> Spectrum {
|
||||||
|
Spectrum::DenselySampled(CIE_Z_DATA.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cie_d65() -> Spectrum {
|
||||||
|
Spectrum::DenselySampled(CIE_D65_DATA.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_spectra_context() -> StandardSpectra {
|
||||||
|
StandardSpectra {
|
||||||
|
x: CIE_X_DATA.view,
|
||||||
|
y: CIE_Y_DATA.view,
|
||||||
|
z: CIE_Z_DATA.view,
|
||||||
|
d65: CIE_D65_DATA.view,
|
||||||
|
cie_y_integral: CIE_Y_INTEGRAL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static SRGB_DATA: Lazy<RGBColorSpaceData> = Lazy::new(|| {
|
||||||
|
let illum = DenselySampledBuffer::new(
|
||||||
|
D65_BUFFER.view.lambda_min,
|
||||||
|
D65_BUFFER.view.lambda_max,
|
||||||
|
D65_BUFFER._storage.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
RGBColorSpaceData::new(
|
||||||
|
Point2f::new(0.64, 0.33),
|
||||||
|
Point2f::new(0.3, 0.6),
|
||||||
|
Point2f::new(0.15, 0.06),
|
||||||
|
illum,
|
||||||
|
RGBToSpectrumTable::srgb(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static DCI_P3: Lazy<RGBColorSpaceData> = Lazy::new(|| {
|
||||||
|
let illum = DenselySampledBuffer::new(
|
||||||
|
D65_BUFFER.view.lambda_min,
|
||||||
|
D65_BUFFER.view.lambda_max,
|
||||||
|
D65_BUFFER._storage.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let r = Point2f::new(0.680, 0.320);
|
||||||
|
let g = Point2f::new(0.265, 0.690);
|
||||||
|
let b = Point2f::new(0.150, 0.060);
|
||||||
|
|
||||||
|
RGBColorSpaceData::new(r, g, b, illum, RGBToSpectrumTable)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static REC2020: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
||||||
|
let illum = DenselySampledBuffer::new(
|
||||||
|
D65_BUFFER.view.lambda_min,
|
||||||
|
D65_BUFFER.view.lambda_max,
|
||||||
|
D65_BUFFER._storage.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let r = Point2f::new(0.708, 0.292);
|
||||||
|
let g = Point2f::new(0.170, 0.797);
|
||||||
|
let b = Point2f::new(0.131, 0.046);
|
||||||
|
|
||||||
|
let table = RGBToSpectrumTable::rec2020();
|
||||||
|
|
||||||
|
RGBColorSpace::new(r, g, b, illum, table)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub static ACES: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
|
||||||
|
let r = Point2f::new(0.7347, 0.2653);
|
||||||
|
let g = Point2f::new(0.0000, 1.0000);
|
||||||
|
let b = Point2f::new(0.0001, -0.0770);
|
||||||
|
let illuminant = Spectrum::std_illuminant_d65();
|
||||||
|
|
||||||
|
let table = RGBToSpectrumTable::aces2065_1();
|
||||||
|
|
||||||
|
RGBColorSpaceData::new(r, g, b, illuminant, table)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn get_colorspace_context() -> StandardColorSpaces {
|
||||||
|
StandardColorSpaces {
|
||||||
|
srgb: &SRGB_DATA.view,
|
||||||
|
dci_p3: &DCI_P3_DATA.view,
|
||||||
|
rec2020: &REC2020.view,
|
||||||
|
aces2065_1: &ACES.view,
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/spectra/piecewise.rs
Normal file
77
src/spectra/piecewise.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
use crate::utils::read_float_file;
|
||||||
|
use shared::Float;
|
||||||
|
use shared::spectra::PiecewiseLinearSpectrum;
|
||||||
|
|
||||||
|
pub struct PiecewiseLinearSpectrumBuffer {
|
||||||
|
pub view: PiecewiseLinearSpectrum,
|
||||||
|
_lambdas: Vec<Float>,
|
||||||
|
_values: Vec<Float>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for PiecewiseLinearSpectrumBuffer {
|
||||||
|
type Target = PiecewiseLinearSpectrum;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PiecewiseLinearSpectrumBuffer {
|
||||||
|
pub fn new(lambdas: Vec<Float>, values: Vec<Float>) -> Self {
|
||||||
|
assert_eq!(lambdas.len(), values.len());
|
||||||
|
let view = PiecewiseLinearSpectrum {
|
||||||
|
lambdas: lambdas.as_ptr(),
|
||||||
|
values: values.as_ptr(),
|
||||||
|
count: lambdas.len() as u32,
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
view,
|
||||||
|
_lambdas: lambdas,
|
||||||
|
_values: values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self {
|
||||||
|
if data.len() % 2 != 0 {
|
||||||
|
panic!("Interleaved data must have an even number of elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut temp: Vec<(Float, Float)> =
|
||||||
|
data.chunks(2).map(|chunk| (chunk[0], chunk[1])).collect();
|
||||||
|
|
||||||
|
temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
|
let (lambdas, values): (Vec<Float>, Vec<Float>) = temp.into_iter().unzip();
|
||||||
|
|
||||||
|
// (Normalization logic usually goes here)
|
||||||
|
|
||||||
|
Self::new(lambdas, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(filepath: &str) -> Option<Self> {
|
||||||
|
let vals = read_float_file(filepath).ok()?;
|
||||||
|
|
||||||
|
if vals.is_empty() || vals.len() % 2 == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = vals.len() / 2;
|
||||||
|
let mut lambdas = Vec::with_capacity(count);
|
||||||
|
let mut values = Vec::with_capacity(count);
|
||||||
|
|
||||||
|
for (_, pair) in vals.chunks(2).enumerate() {
|
||||||
|
let curr_lambda = pair[0];
|
||||||
|
let curr_val = pair[1];
|
||||||
|
|
||||||
|
if let Some(&prev_lambda) = lambdas.last() {
|
||||||
|
if curr_lambda <= prev_lambda {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lambdas.push(curr_lambda);
|
||||||
|
values.push(curr_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self::new(lambdas, values))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,9 @@
|
||||||
pub mod bilerp;
|
|
||||||
pub mod checkerboard;
|
|
||||||
pub mod constant;
|
|
||||||
pub mod dots;
|
|
||||||
pub mod fbm;
|
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod marble;
|
|
||||||
pub mod mix;
|
pub mod mix;
|
||||||
pub mod ptex;
|
pub mod ptex;
|
||||||
pub mod scale;
|
pub mod scaled;
|
||||||
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 image::*;
|
||||||
pub use marble::*;
|
|
||||||
pub use mix::*;
|
pub use mix::*;
|
||||||
pub use ptex::*;
|
pub use ptex::*;
|
||||||
pub use scale::*;
|
pub use scaled::*;
|
||||||
pub use windy::*;
|
|
||||||
pub use wrinkled::*;
|
|
||||||
|
|
|
||||||
26
src/utils/containers.rs
Normal file
26
src/utils/containers.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
pub struct InternCache<T> {
|
||||||
|
cache: Mutex<HashSet<Arc<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> InternCache<T>
|
||||||
|
where
|
||||||
|
T: Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
cache: Mutex::new(HashSet::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(&self, value: T) -> Arc<T> {
|
||||||
|
let mut lock = self.cache.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(existing) = lock.get(&value) {
|
||||||
|
return existing.clone(); // Returns a cheap Arc copy
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_item = Arc::new(value);
|
||||||
|
lock.insert(new_item.clone());
|
||||||
|
new_item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,31 @@ fn set_search_directory(filename: &str) {
|
||||||
let _ = SEARCH_DIRECTORY.set(dir);
|
let _ = SEARCH_DIRECTORY.set(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolve_filename(filename: &str) -> String {
|
||||||
|
let search_directory = SEARCH_DIRECTORY
|
||||||
|
.get()
|
||||||
|
.map(|p| p.as_path())
|
||||||
|
.unwrap_or(Path::new(""));
|
||||||
|
|
||||||
|
if search_directory.as_os_str().is_empty()
|
||||||
|
|| filename.is_empty()
|
||||||
|
|| Path::new(filename).is_absolute()
|
||||||
|
{
|
||||||
|
return filename.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let filepath = search_directory.join(filename);
|
||||||
|
if filepath.exists() {
|
||||||
|
filepath
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap_or_else(|_| filepath.to_path_buf())
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
filename.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
|
pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
|
||||||
let content = fs::read_to_string(filename)?;
|
let content = fs::read_to_string(filename)?;
|
||||||
|
|
||||||
|
|
@ -45,28 +70,3 @@ pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
|
||||||
|
|
||||||
Ok(values)
|
Ok(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_filename(filename: &str) -> String {
|
|
||||||
let search_directory = SEARCH_DIRECTORY
|
|
||||||
.get()
|
|
||||||
.map(|p| p.as_path())
|
|
||||||
.unwrap_or(Path::new(""));
|
|
||||||
|
|
||||||
if search_directory.as_os_str().is_empty()
|
|
||||||
|| filename.is_empty()
|
|
||||||
|| Path::new(filename).is_absolute()
|
|
||||||
{
|
|
||||||
return filename.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let filepath = search_directory.join(filename);
|
|
||||||
if filepath.exists() {
|
|
||||||
filepath
|
|
||||||
.canonicalize()
|
|
||||||
.unwrap_or_else(|_| filepath.to_path_buf())
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
filename.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod containers;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue