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]
|
||||
default = []
|
||||
use_f64 = []
|
||||
cuda = ["cuda_std", "cust", "cuda_builder", "shared/cuda", ]
|
||||
use_nvtx = []
|
||||
cuda = ["cust", "cuda_builder", "shared/cuda", ]
|
||||
|
||||
[dependencies]
|
||||
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 }
|
||||
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]
|
||||
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
|
||||
shared = { path = "../shared" }
|
||||
shared = { path = "../shared", features = ["cuda"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
|
|
|||
|
|
@ -1,34 +1,31 @@
|
|||
use crate::core::camera::{
|
||||
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform,
|
||||
};
|
||||
use crate::core::film::{Film, FilmTrait};
|
||||
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::core::film::Film;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::medium::Medium;
|
||||
use crate::core::options::get_options;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::CameraSample;
|
||||
use crate::images::ImageMetadata;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::parameters::ParameterDictionary;
|
||||
use crate::utils::Transform;
|
||||
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 base: CameraBase,
|
||||
pub screen_from_camera: TransformGeneric<Float>,
|
||||
pub camera_from_raster: TransformGeneric<Float>,
|
||||
pub raster_from_screen: TransformGeneric<Float>,
|
||||
pub screen_from_raster: TransformGeneric<Float>,
|
||||
pub screen_from_camera: Transform,
|
||||
pub camera_from_raster: Transform,
|
||||
pub raster_from_screen: Transform,
|
||||
pub screen_from_raster: Transform,
|
||||
pub lens_radius: Float,
|
||||
pub focal_distance: Float,
|
||||
pub dx_camera: Vector3f,
|
||||
pub dy_camera: Vector3f,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl OrthographicCamera {
|
||||
pub fn new(
|
||||
base: CameraBase,
|
||||
|
|
@ -36,21 +33,30 @@ impl OrthographicCamera {
|
|||
lens_radius: Float,
|
||||
focal_distance: Float,
|
||||
) -> 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.y() - screen_window.p_min.y()),
|
||||
1.,
|
||||
) * TransformGeneric::translate(
|
||||
Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.),
|
||||
);
|
||||
let raster_from_ndc = TransformGeneric::scale(
|
||||
base.film.full_resolution().x() as Float,
|
||||
-base.film.full_resolution().y() as Float,
|
||||
) * Transform::translate(Vector3f::new(
|
||||
-screen_window.p_min.x(),
|
||||
-screen_window.p_max.y(),
|
||||
0.,
|
||||
));
|
||||
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.,
|
||||
);
|
||||
|
||||
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
||||
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 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.));
|
||||
|
|
@ -71,58 +77,16 @@ impl OrthographicCamera {
|
|||
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 {
|
||||
fn base(&self) -> &CameraBase {
|
||||
&self.base
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn init_metadata(&self, metadata: &mut ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
}
|
||||
|
||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
fn base(&self) -> &CameraBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn generate_ray(
|
||||
|
|
@ -140,18 +104,14 @@ impl CameraTrait for OrthographicCamera {
|
|||
Some(self.sample_time(sample.time)),
|
||||
self.base().medium.clone(),
|
||||
);
|
||||
// Modify ray for depth of field
|
||||
if self.lens_radius > 0. {
|
||||
// Sample point on lens
|
||||
let p_lens_vec =
|
||||
self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens));
|
||||
let p_lens = Point2f::from(p_lens_vec);
|
||||
|
||||
// Compute point on plane of focus
|
||||
let ft = self.focal_distance / ray.d.z();
|
||||
let p_focus = ray.at(ft);
|
||||
|
||||
// Update ray for effect of lens
|
||||
ray.o = Point3f::new(p_lens.x(), p_lens.y(), 0.);
|
||||
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 rd = RayDifferential::default();
|
||||
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 {
|
||||
let time = self.sample_time(sample.time);
|
||||
let world_dx = self
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
use super::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::camera::CameraBaseParameters;
|
||||
use crate::core::film::{Film, FilmTrait};
|
||||
use crate::core::filter::FilterTrait;
|
||||
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::core::film::Film;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::medium::Medium;
|
||||
use crate::core::options::get_options;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::CameraSample;
|
||||
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::transform::Transform;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PerspectiveCamera {
|
||||
|
|
@ -30,6 +24,7 @@ pub struct PerspectiveCamera {
|
|||
pub cos_total_width: Float,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl PerspectiveCamera {
|
||||
pub fn new(
|
||||
base: CameraBase,
|
||||
|
|
@ -80,61 +75,18 @@ impl PerspectiveCamera {
|
|||
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 {
|
||||
fn base(&self) -> &CameraBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
impl PerspectiveCamera {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
}
|
||||
|
||||
fn base(&self) -> &CameraBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn generate_ray(
|
||||
&self,
|
||||
sample: CameraSample,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
use super::{
|
||||
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, ExitPupilSample,
|
||||
LensElementInterface,
|
||||
};
|
||||
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::{
|
||||
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::sampler::CameraSample;
|
||||
use crate::core::scattering::refract;
|
||||
use crate::image::{Image, PixelFormat};
|
||||
use crate::images::{Image, PixelFormat};
|
||||
use crate::spectra::color::SRGB;
|
||||
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::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 {
|
||||
base: CameraBase,
|
||||
focus_distance: Float,
|
||||
set_aperture_diameter: Float,
|
||||
aperture_image: Option<Image>,
|
||||
element_interface: Vec<LensElementInterface>,
|
||||
aperture_image: *const Image,
|
||||
element_interfaces: *const LensElementInterface,
|
||||
n_elements: usize,
|
||||
physical_extent: Bounds2f,
|
||||
exit_pupil_bounds: Vec<Bounds2f>,
|
||||
exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl RealisticCamera {
|
||||
pub fn new(
|
||||
base: CameraBase,
|
||||
lens_params: Vec<Float>,
|
||||
lens_params: &[Float],
|
||||
focus_distance: Float,
|
||||
set_aperture_diameter: Float,
|
||||
aperture_image: Option<Image>,
|
||||
) -> Self {
|
||||
let aspect =
|
||||
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
|
||||
let diagonal = base.film.diagonal();
|
||||
let film_ptr = base.film;
|
||||
if film_ptr.is_null() {
|
||||
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 y = x * aspect;
|
||||
let physical_extent =
|
||||
|
|
@ -73,19 +91,24 @@ impl RealisticCamera {
|
|||
}
|
||||
|
||||
let n_samples = 64;
|
||||
let half_diag = base.film.diagonal() / 2.0;
|
||||
let exit_pupil_bounds: Vec<_> = (0..n_samples)
|
||||
.map(|i| {
|
||||
let r0 = (i as Float / n_samples as Float) * half_diag;
|
||||
let r1 = ((i + 1) as Float / n_samples as Float) * half_diag;
|
||||
Self::compute_exit_pupil_bounds(&element_interface, r0, r1)
|
||||
})
|
||||
.collect();
|
||||
let half_diag = film.diagonal() / 2.0;
|
||||
let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
|
||||
|
||||
for i in 0..EXIT_PUPIL_SAMPLES {
|
||||
let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
|
||||
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);
|
||||
}
|
||||
|
||||
let n_elements = element_interface.len();
|
||||
let element_interfaces = element_interface.as_ptr();
|
||||
std::mem::forget(element_interface);
|
||||
|
||||
Self {
|
||||
base,
|
||||
focus_distance,
|
||||
element_interface,
|
||||
element_interfaces,
|
||||
n_elements,
|
||||
physical_extent,
|
||||
set_aperture_diameter,
|
||||
aperture_image,
|
||||
|
|
@ -93,20 +116,58 @@ impl RealisticCamera {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn lens_rear_z(&self) -> Float {
|
||||
self.element_interface.last().unwrap().thickness
|
||||
pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) {
|
||||
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 {
|
||||
let mut z_sum = 0.;
|
||||
for element in &self.element_interface {
|
||||
z_sum += element.thickness;
|
||||
pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) {
|
||||
let x = 0.001 * self.get_film().diagonal();
|
||||
let r_scene = Ray::new(
|
||||
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 {
|
||||
self.element_interface.last().unwrap().aperture_radius
|
||||
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
||||
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
|
||||
}
|
||||
|
||||
fn compute_exit_pupil_bounds(
|
||||
|
|
@ -160,38 +221,40 @@ impl RealisticCamera {
|
|||
pupil_bounds
|
||||
}
|
||||
|
||||
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
||||
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
|
||||
}
|
||||
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
|
||||
// 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(
|
||||
radius: Float,
|
||||
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 {
|
||||
let pupil_bounds = self.exit_pupil_bounds[r_index];
|
||||
if pupil_bounds.is_degenerate() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let p_hit_relative = o + ray.d * t;
|
||||
// Ensures the normal points towards the incident ray.
|
||||
let n = Normal3f::from(Vector3f::from(p_hit_relative))
|
||||
.normalize()
|
||||
.face_forward(-ray.d);
|
||||
// Generate sample point inside exit pupil bound
|
||||
let p_lens = pupil_bounds.lerp(u_lens);
|
||||
let pdf = 1. / pupil_bounds.area();
|
||||
|
||||
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)> {
|
||||
|
|
@ -274,229 +337,72 @@ impl RealisticCamera {
|
|||
Some((weight, r_out))
|
||||
}
|
||||
|
||||
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
|
||||
// Find exit pupil bound for sample distance from film center
|
||||
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
|
||||
let mut r_index =
|
||||
(r_film / (self.base.film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
|
||||
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
|
||||
fn intersect_spherical_element(
|
||||
radius: Float,
|
||||
z_center: Float,
|
||||
ray: &Ray,
|
||||
) -> Option<(Float, Normal3f)> {
|
||||
let o = ray.o - Vector3f::new(0.0, 0.0, z_center);
|
||||
|
||||
let pupil_bounds = self.exit_pupil_bounds[r_index];
|
||||
if pupil_bounds.is_degenerate() {
|
||||
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;
|
||||
}
|
||||
|
||||
// Generate sample point inside exit pupil bound
|
||||
let p_lens = pupil_bounds.lerp(u_lens);
|
||||
let pdf = 1. / pupil_bounds.area();
|
||||
let p_hit_relative = o + ray.d * t;
|
||||
// Ensures the normal points towards the incident ray.
|
||||
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
|
||||
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 })
|
||||
Some((t, n))
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
params: &ParameterDictionary,
|
||||
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", "");
|
||||
pub fn lens_rear_z(&self) -> Float {
|
||||
self.element_interface.last().unwrap().thickness
|
||||
}
|
||||
|
||||
if lens_file.is_empty() {
|
||||
return Err(format!("{}: No lens file supplied", loc));
|
||||
pub fn lens_front_z(&self) -> Float {
|
||||
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())?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self::new(
|
||||
base,
|
||||
lens_params,
|
||||
focal_distance,
|
||||
aperture_diameter,
|
||||
aperture_image,
|
||||
))
|
||||
pub fn rear_element_radius(&self) -> Float {
|
||||
self.element_interface.last().unwrap().aperture_radius
|
||||
}
|
||||
}
|
||||
|
||||
impl CameraTrait for RealisticCamera {
|
||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
}
|
||||
|
||||
fn base(&self) -> &CameraBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
fn get_film(&self) -> &Film {
|
||||
#[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(
|
||||
|
|
@ -505,9 +411,10 @@ impl CameraTrait for RealisticCamera {
|
|||
_lambda: &SampledWavelengths,
|
||||
) -> Option<CameraRay> {
|
||||
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
|
||||
let film = self.get_film();
|
||||
let s = Point2f::new(
|
||||
sample.p_film.x() / self.base.film.full_resolution().x() as Float,
|
||||
sample.p_film.y() / self.base.film.full_resolution().y() as Float,
|
||||
sample.p_film.x() / film.full_resolution().x() as Float,
|
||||
sample.p_film.y() / film.full_resolution().y() as Float,
|
||||
);
|
||||
let p_film2 = self.physical_extent.lerp(s);
|
||||
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::film::{Film, FilmTrait};
|
||||
use crate::core::camera::{CameraBase, CameraRay, CameraTransform};
|
||||
use crate::core::film::Film;
|
||||
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
|
||||
use crate::core::medium::Medium;
|
||||
use crate::core::options::get_options;
|
||||
use crate::core::pbrt::{Float, PI};
|
||||
use crate::core::sampler::CameraSample;
|
||||
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::parameters::ParameterDictionary;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum Mapping {
|
||||
EquiRectangular,
|
||||
EqualArea,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SphericalCamera {
|
||||
pub base: CameraBase,
|
||||
pub screen: Bounds2f,
|
||||
pub lens_radius: Float,
|
||||
pub focal_distance: Float,
|
||||
pub mapping: Mapping,
|
||||
pub base: CameraBase,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl SphericalCamera {
|
||||
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", 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,
|
||||
})
|
||||
pub fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +33,16 @@ impl CameraTrait for SphericalCamera {
|
|||
&self.base
|
||||
}
|
||||
|
||||
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
|
||||
self.base.init_metadata(metadata)
|
||||
fn get_film(&self) -> &Film {
|
||||
#[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(
|
||||
|
|
@ -104,9 +51,10 @@ impl CameraTrait for SphericalCamera {
|
|||
_lamdba: &SampledWavelengths,
|
||||
) -> Option<CameraRay> {
|
||||
// Compute spherical camera ray direction
|
||||
let film = self.get_film();
|
||||
let mut uv = Point2f::new(
|
||||
sample.p_film.x() / self.base().film.full_resolution().x() as Float,
|
||||
sample.p_film.y() / self.base().film.full_resolution().y() as Float,
|
||||
sample.p_film.x() / film.full_resolution().x() as Float,
|
||||
sample.p_film.y() / film.full_resolution().y() as Float,
|
||||
);
|
||||
let dir: Vector3f;
|
||||
if self.mapping == Mapping::EquiRectangular {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use std::cmp::Ordering;
|
|||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SplitMethod {
|
||||
SAH,
|
||||
|
|
@ -18,10 +19,11 @@ pub enum SplitMethod {
|
|||
EqualCounts,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
struct BVHSplitBucket {
|
||||
count: usize,
|
||||
bounds: Bounds3f,
|
||||
pub count: usize,
|
||||
pub bounds: Bounds3f,
|
||||
}
|
||||
|
||||
#[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::{
|
||||
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
|
||||
};
|
||||
|
|
@ -9,34 +10,31 @@ use crate::core::pbrt::Float;
|
|||
use crate::core::sampler::CameraSample;
|
||||
use crate::images::ImageMetadata;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::error::FileLoc;
|
||||
use crate::utils::math::lerp;
|
||||
use crate::utils::parameters::ParameterDictionary;
|
||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CameraRay {
|
||||
pub ray: Ray,
|
||||
pub weight: SampledSpectrum,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CameraWiSample {
|
||||
wi_spec: SampledSpectrum,
|
||||
wi: Vector3f,
|
||||
pdf: Float,
|
||||
p_raster: Point2f,
|
||||
p_ref: Interaction,
|
||||
p_lens: Interaction,
|
||||
pub wi_spec: SampledSpectrum,
|
||||
pub wi: Vector3f,
|
||||
pub pdf: Float,
|
||||
pub p_raster: Point2f,
|
||||
pub p_ref: Interaction,
|
||||
pub p_lens: Interaction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CameraTransform {
|
||||
pub render_from_camera: AnimatedTransform,
|
||||
pub world_from_render: Transform,
|
||||
|
|
@ -106,55 +104,20 @@ impl CameraTransform {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
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)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CameraBase {
|
||||
pub camera_transform: CameraTransform,
|
||||
pub shutter_open: Float,
|
||||
pub shutter_close: Float,
|
||||
pub film: Arc<Film>,
|
||||
pub medium: Option<Arc<Medium>>,
|
||||
pub film: *const Film,
|
||||
pub medium: *const Medium,
|
||||
pub min_pos_differential_x: Vector3f,
|
||||
pub min_pos_differential_y: Vector3f,
|
||||
pub min_dir_differential_x: Vector3f,
|
||||
pub min_dir_differential_y: Vector3f,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl CameraBase {
|
||||
pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
|
||||
let camera_from_world: Transform =
|
||||
|
|
@ -162,83 +125,97 @@ impl CameraBase {
|
|||
|
||||
metadata.camera_from_world = Some(camera_from_world.get_matrix());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(p: CameraBaseParameters) -> Self {
|
||||
Self {
|
||||
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,
|
||||
min_pos_differential_x: Vector3f::default(),
|
||||
min_pos_differential_y: Vector3f::default(),
|
||||
min_dir_differential_x: Vector3f::default(),
|
||||
min_dir_differential_y: Vector3f::default(),
|
||||
}
|
||||
}
|
||||
#[enum_dispatch(CameraTrait)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Camera {
|
||||
Perspective(PerspectiveCamera),
|
||||
Orthographic(OrthographicCamera),
|
||||
Spherical(SphericalCamera),
|
||||
Realistic(RealisticCamera),
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait CameraTrait {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn init_metadata(&self, metadata: &mut ImageMetadata);
|
||||
|
||||
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 {
|
||||
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(
|
||||
&self,
|
||||
sample: CameraSample,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> Option<CameraRay> {
|
||||
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;
|
||||
match self {
|
||||
Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
|
||||
_ => {
|
||||
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] {
|
||||
let mut s_shift = sample;
|
||||
s_shift.p_film[0] += eps;
|
||||
for eps in [0.05, -0.05] {
|
||||
let mut s_shift = sample;
|
||||
s_shift.p_film[0] += eps;
|
||||
|
||||
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||
rd.rx_origin =
|
||||
central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||
rd.rx_direction =
|
||||
central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||
rx_found = true;
|
||||
break;
|
||||
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||
rd.rx_origin = central_cam_ray.ray.o
|
||||
+ (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||
rd.rx_direction = central_cam_ray.ray.d
|
||||
+ (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||
rx_found = true;
|
||||
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(
|
||||
|
|
@ -290,65 +267,4 @@ pub trait CameraTrait {
|
|||
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::pbrt::Float;
|
||||
use crate::spectra::Spectrum;
|
||||
use crate::utils::math::{SquareMatrix, evaluate_polynomial, lerp};
|
||||
use once_cell::sync::Lazy;
|
||||
use crate::core::pbrt::{Float, find_interval};
|
||||
use crate::core::spectrum::Spectrum;
|
||||
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
pub trait Triplet {
|
||||
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct XYZ {
|
||||
pub x: Float,
|
||||
|
|
@ -23,9 +18,9 @@ pub struct XYZ {
|
|||
pub z: Float,
|
||||
}
|
||||
|
||||
impl Triplet for XYZ {
|
||||
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self {
|
||||
XYZ::new(c1, c2, c3)
|
||||
impl From<(Float, Float, Float)> for XYZ {
|
||||
fn from(triplet: (Float, Float, Float)) -> Self {
|
||||
XYZ::new(triplet.0, triplet.1, triplet.2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -259,9 +254,9 @@ pub struct RGB {
|
|||
pub b: Float,
|
||||
}
|
||||
|
||||
impl Triplet for RGB {
|
||||
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self {
|
||||
RGB::new(c1, c2, c3)
|
||||
impl From<(Float, Float, Float)> for RGB {
|
||||
fn from(triplet: (Float, Float, Float)) -> Self {
|
||||
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;
|
||||
|
||||
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;
|
||||
fn mul(self, v: RGB) -> XYZ {
|
||||
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;
|
||||
}
|
||||
|
||||
impl MatrixMulColor for SquareMatrix<Float, 3> {
|
||||
impl MatrixMulColor for SquareMatrix3f {
|
||||
fn mul_rgb(&self, v: RGB) -> RGB {
|
||||
let m = self;
|
||||
RGB::new(
|
||||
|
|
@ -513,65 +508,22 @@ impl MatrixMulColor for SquareMatrix<Float, 3> {
|
|||
}
|
||||
}
|
||||
|
||||
pub const RES: usize = 64;
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct RGBSigmoidPolynomial {
|
||||
c0: Float,
|
||||
c1: Float,
|
||||
c2: Float,
|
||||
pub c0: Float,
|
||||
pub c1: Float,
|
||||
pub c2: Float,
|
||||
}
|
||||
|
||||
impl RGBSigmoidPolynomial {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(c0: Float, c1: Float, c2: Float) -> Self {
|
||||
Self { c0, c1, c2 }
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, lambda: Float) -> Float {
|
||||
let eval = match evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
panic!("evaluate_polynomial returned None with non-empty coefficients")
|
||||
}
|
||||
};
|
||||
|
||||
let eval = evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]);
|
||||
Self::s(eval)
|
||||
}
|
||||
|
||||
|
|
@ -586,120 +538,16 @@ impl RGBSigmoidPolynomial {
|
|||
|
||||
fn s(x: Float) -> Float {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display {
|
||||
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)]
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum ColorEncoding {
|
||||
|
|
@ -723,6 +572,7 @@ impl fmt::Display for ColorEncoding {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct LinearEncoding;
|
||||
impl ColorEncodingTrait for LinearEncoding {
|
||||
|
|
@ -747,6 +597,7 @@ impl fmt::Display for LinearEncoding {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct SRGBEncoding;
|
||||
impl ColorEncodingTrait for SRGBEncoding {
|
||||
|
|
@ -1045,3 +896,122 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [
|
|||
0.9911022186,
|
||||
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::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
|
||||
use crate::core::filter::Filter;
|
||||
use crate::core::geometry::{
|
||||
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::pbrt::Float;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
|
||||
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::{
|
||||
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
||||
PiecewiseLinearSpectrum, RGB, RGBColorSpace, SampledSpectrum, SampledWavelengths, Spectrum,
|
||||
SpectrumTrait, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum,
|
||||
PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
|
||||
get_named_spectrum,
|
||||
};
|
||||
use crate::utils::AtomicFloat;
|
||||
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::sampling::VarianceEstimator;
|
||||
use crate::utils::transform::AnimatedTransform;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
|
@ -30,7 +29,7 @@ pub struct RGBFilm {
|
|||
pub write_fp16: bool,
|
||||
pub filter_integral: Float,
|
||||
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
pub pixels: Arc<Array2D<RGBPixel>>,
|
||||
pub pixels: Array2D<RGBPixel>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -53,7 +52,7 @@ impl RGBFilm {
|
|||
if sensor_ptr.is_null() {
|
||||
panic!("Film must have a sensor");
|
||||
}
|
||||
let sensor = unsafe { &*self.sensor };
|
||||
let sensor = unsafe { &*sensor_ptr };
|
||||
let filter_integral = base.filter.integral();
|
||||
let sensor_matrix = sensor.xyz_from_sensor_rgb;
|
||||
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
|
||||
|
|
@ -75,7 +74,7 @@ impl RGBFilm {
|
|||
write_fp16,
|
||||
filter_integral,
|
||||
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
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn get_sensor(&self) -> Result<&PixelSensor, String> {
|
||||
if self.sensor.is_null() {
|
||||
return Err("FilmBase error: PixelSensor pointer is null.".to_string());
|
||||
pub fn get_sensor(&self) -> &PixelSensor {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
{
|
||||
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(
|
||||
|
|
@ -259,27 +262,20 @@ impl GBufferFilm {
|
|||
&mut self.base
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
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 {
|
||||
pub fn get_sensor(&self) -> &PixelSensor {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
{
|
||||
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."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&*self.sensor
|
||||
unsafe { &*self.sensor }
|
||||
}
|
||||
|
||||
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 m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||
if m > self.max_component_value {
|
||||
|
|
@ -310,7 +306,7 @@ impl GBufferFilm {
|
|||
}
|
||||
|
||||
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);
|
||||
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
|
||||
}
|
||||
|
|
@ -467,15 +463,15 @@ pub struct PixelSensor {
|
|||
pub imaging_ratio: Float,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl PixelSensor {
|
||||
const N_SWATCH_REFLECTANCES: usize = 24;
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(
|
||||
r: Spectrum,
|
||||
g: Spectrum,
|
||||
b: Spectrum,
|
||||
output_colorspace: RGBColorSpace,
|
||||
sensor_illum: Option<Arc<Spectrum>>,
|
||||
sensor_illum: Option<std::sync::Arc<Spectrum>>,
|
||||
imaging_ratio: Float,
|
||||
swatches: &[Spectrum; 24],
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
|
|
@ -490,11 +486,11 @@ impl PixelSensor {
|
|||
let r_bar = DenselySampledSpectrum::from_spectrum(&r);
|
||||
let g_bar = DenselySampledSpectrum::from_spectrum(&g);
|
||||
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();
|
||||
|
||||
for i in 0..N_SWATCH_REFLECTANCES {
|
||||
for i in 0..Self::N_SWATCH_REFLECTANCES {
|
||||
let rgb = Self::project_reflectance::<RGB>(
|
||||
&swatches[i],
|
||||
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_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 xyz = Self::project_reflectance::<XYZ>(
|
||||
&s,
|
||||
|
|
@ -537,7 +533,7 @@ impl PixelSensor {
|
|||
|
||||
pub fn new_with_white_balance(
|
||||
output_colorspace: &RGBColorSpace,
|
||||
sensor_illum: Option<Arc<Spectrum>>,
|
||||
sensor_illum: Option<std::sync::Arc<Spectrum>>,
|
||||
imaging_ratio: Float,
|
||||
) -> Self {
|
||||
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,
|
||||
illum: &Spectrum,
|
||||
b1: &Spectrum,
|
||||
b2: &Spectrum,
|
||||
b3: &Spectrum,
|
||||
) -> T {
|
||||
) -> T
|
||||
where
|
||||
T: From<[Float; 3]>,
|
||||
{
|
||||
let mut result = [0.; 3];
|
||||
let mut g_integral = 0.;
|
||||
|
||||
|
|
@ -590,7 +589,7 @@ impl PixelSensor {
|
|||
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 {
|
||||
fn base(&self) -> &FilmBase {
|
||||
pub fn base(&self) -> &FilmBase {
|
||||
match self {
|
||||
Film::RGB(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 {
|
||||
Film::RGB(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::pbrt::Float;
|
||||
use crate::filters::*;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
|
|
@ -12,13 +13,13 @@ pub struct FilterSample {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FilterSampler {
|
||||
domain: Bounds2f,
|
||||
distrib: PiecewiseConstant2D,
|
||||
f: Array2D<Float>,
|
||||
pub domain: Bounds2f,
|
||||
pub distrib: PiecewiseConstant2D,
|
||||
pub f: Array2D<Float>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl FilterSampler {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new<F>(radius: Vector2f, func: F) -> Self
|
||||
where
|
||||
F: Fn(Point2f) -> Float,
|
||||
|
|
@ -44,9 +45,7 @@ impl FilterSampler {
|
|||
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
||||
Self { domain, f, distrib }
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterSampler {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
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)]
|
||||
pub enum Filter {
|
||||
Box(BoxFilter),
|
||||
|
|
@ -67,44 +75,3 @@ pub enum Filter {
|
|||
LanczosSinc(LanczosSincFilter),
|
||||
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::pbrt::Float;
|
||||
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 o: Point3f,
|
||||
pub d: Vector3f,
|
||||
pub medium: Option<Arc<Medium>>,
|
||||
pub medium: *const Medium,
|
||||
pub time: Float,
|
||||
// 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 {
|
||||
|
|
@ -27,7 +27,7 @@ impl Default for 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 {
|
||||
o,
|
||||
d,
|
||||
|
|
@ -110,6 +110,7 @@ impl Ray {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct RayDifferential {
|
||||
pub rx_origin: Point3f,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::camera::{Camera, CameraTrait};
|
||||
use crate::core::bssrdf::BSSRDF;
|
||||
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
|
||||
use crate::core::camera::Camera;
|
||||
use crate::core::geometry::{
|
||||
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::light::Light;
|
||||
use crate::core::material::{
|
||||
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::pbrt::Float;
|
||||
use crate::core::sampler::{Sampler, SamplerTrait};
|
||||
use crate::core::texture::{FloatTexture, UniversalTextureEvaluator};
|
||||
use crate::image::Image;
|
||||
use crate::lights::{Light, LightTrait};
|
||||
use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
|
||||
use crate::images::Image;
|
||||
use crate::shapes::Shape;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::math::{clamp, difference_of_products, square};
|
||||
|
||||
use bumpalo::Bump;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
pub struct InteractionData {
|
||||
pub pi: Point3fi,
|
||||
pub n: Normal3f,
|
||||
pub time: Float,
|
||||
pub wo: Vector3f,
|
||||
pub medium_interface: Option<MediumInterface>,
|
||||
pub medium: Option<Arc<Medium>>,
|
||||
pub medium_interface: MediumInterface,
|
||||
pub medium: *const Medium,
|
||||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
|
|
@ -63,16 +62,16 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
|
|||
false
|
||||
}
|
||||
|
||||
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> {
|
||||
fn get_medium(&self, w: Vector3f) -> *const Medium {
|
||||
let data = self.get_common();
|
||||
if let Some(mi) = &data.medium_interface {
|
||||
if w.dot(data.n.into()) > 0.0 {
|
||||
mi.outside.clone()
|
||||
mi.outside
|
||||
} else {
|
||||
mi.inside.clone()
|
||||
mi.inside
|
||||
}
|
||||
} else {
|
||||
data.medium.clone()
|
||||
data.medium
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,9 +90,8 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
|
|||
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 other_data = other.get_common();
|
||||
|
||||
let mut ray =
|
||||
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)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Interaction {
|
||||
Surface(SurfaceInteraction),
|
||||
Medium(MediumInteraction),
|
||||
|
|
@ -128,6 +127,7 @@ impl Interaction {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimpleInteraction {
|
||||
pub common: InteractionData,
|
||||
|
|
@ -171,8 +171,9 @@ impl InteractionTrait for SimpleInteraction {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Shadinggeom {
|
||||
#[repr(C)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct ShadingGeom {
|
||||
pub n: Normal3f,
|
||||
pub dpdu: Vector3f,
|
||||
pub dpdv: Vector3f,
|
||||
|
|
@ -180,7 +181,8 @@ pub struct Shadinggeom {
|
|||
pub dndv: Normal3f,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct SurfaceInteraction {
|
||||
pub common: InteractionData,
|
||||
pub uv: Point2f,
|
||||
|
|
@ -188,19 +190,22 @@ pub struct SurfaceInteraction {
|
|||
pub dpdv: Vector3f,
|
||||
pub dndu: Normal3f,
|
||||
pub dndv: Normal3f,
|
||||
pub shading: Shadinggeom,
|
||||
pub face_index: usize,
|
||||
pub area_light: Option<Arc<Light>>,
|
||||
pub material: Option<Arc<Material>>,
|
||||
pub shading: ShadingGeom,
|
||||
pub face_index: u32,
|
||||
pub area_light: *const Light,
|
||||
pub material: *const Material,
|
||||
pub shape: *const Shape,
|
||||
pub dpdx: Vector3f,
|
||||
pub dpdy: Vector3f,
|
||||
pub dudx: Float,
|
||||
pub dvdx: Float,
|
||||
pub dudy: Float,
|
||||
pub dvdy: Float,
|
||||
pub shape: Arc<Shape>,
|
||||
}
|
||||
|
||||
unsafe impl Send for SurfaceInteraction {}
|
||||
unsafe impl Sync for SurfaceInteraction {}
|
||||
|
||||
impl SurfaceInteraction {
|
||||
pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
if let Some(area_light) = &self.area_light {
|
||||
|
|
@ -304,6 +309,7 @@ impl SurfaceInteraction {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn get_bsdf(
|
||||
&mut self,
|
||||
r: &Ray,
|
||||
|
|
@ -343,12 +349,12 @@ impl SurfaceInteraction {
|
|||
Some(bsdf)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn get_bssrdf(
|
||||
&self,
|
||||
_ray: &Ray,
|
||||
lambda: &SampledWavelengths,
|
||||
_camera: &Camera,
|
||||
_scratch: &Bump,
|
||||
) -> Option<BSSRDF> {
|
||||
let material = {
|
||||
let root_mat = self.material.as_deref()?;
|
||||
|
|
@ -370,8 +376,8 @@ impl SurfaceInteraction {
|
|||
fn compute_bump_geom(
|
||||
&mut self,
|
||||
tex_eval: &UniversalTextureEvaluator,
|
||||
displacement: Option<FloatTexture>,
|
||||
normal_image: Option<Arc<Image>>,
|
||||
displacement: *const GPUFloatTexture,
|
||||
normal_image: *const Image,
|
||||
) {
|
||||
let ctx = NormalBumpEvalContext::from(&*self);
|
||||
let (dpdu, dpdv) = if let Some(disp) = displacement {
|
||||
|
|
@ -494,12 +500,12 @@ impl InteractionTrait for SurfaceInteraction {
|
|||
&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| {
|
||||
if self.n().dot(w.into()) > 0.0 {
|
||||
interface.outside.clone()
|
||||
interface.outside
|
||||
} else {
|
||||
interface.inside.clone()
|
||||
interface.inside
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -543,7 +549,7 @@ impl SurfaceInteraction {
|
|||
dpdv,
|
||||
dndu,
|
||||
dndv,
|
||||
shading: Shadinggeom {
|
||||
shading: ShadingGeom {
|
||||
n: shading_n,
|
||||
dpdu,
|
||||
dpdv,
|
||||
|
|
@ -559,7 +565,7 @@ impl SurfaceInteraction {
|
|||
dudy: 0.0,
|
||||
dvdx: 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(
|
||||
&mut self,
|
||||
mtl: Arc<Material>,
|
||||
area: Arc<Light>,
|
||||
prim_medium_interface: Option<MediumInterface>,
|
||||
ray_medium: Arc<Medium>,
|
||||
mtl: *const Material,
|
||||
area: *const Light,
|
||||
prim_medium_interface: MediumInterface,
|
||||
ray_medium: *const Medium,
|
||||
) {
|
||||
self.material = Some(mtl);
|
||||
self.area_light = Some(area);
|
||||
if prim_medium_interface
|
||||
.as_ref()
|
||||
.is_some_and(|mi| mi.is_medium_transition())
|
||||
{
|
||||
self.common.medium_interface = prim_medium_interface;
|
||||
self.material = mtl;
|
||||
self.area_light = area;
|
||||
|
||||
if prim_medium_interface.is_medium_transition() {
|
||||
self.common.medium_interface = *prim_medium_interface;
|
||||
} 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 common: InteractionData,
|
||||
pub medium: Arc<Medium>,
|
||||
pub medium: *const Medium,
|
||||
pub phase: PhaseFunction,
|
||||
pub medium_interface: MediumInterface,
|
||||
}
|
||||
|
||||
impl MediumInteraction {
|
||||
|
|
@ -657,7 +664,7 @@ impl MediumInteraction {
|
|||
p: Point3f,
|
||||
wo: Vector3f,
|
||||
time: Float,
|
||||
medium: Arc<Medium>,
|
||||
medium: *const Medium,
|
||||
phase: PhaseFunction,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
@ -667,10 +674,11 @@ impl MediumInteraction {
|
|||
time,
|
||||
wo: wo.normalize(),
|
||||
medium_interface: None,
|
||||
medium: Some(medium.clone()),
|
||||
medium,
|
||||
},
|
||||
medium,
|
||||
phase,
|
||||
medium_interface: MediumInterface::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,30 @@
|
|||
use crate::core::color::RGB;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
||||
Vector3f, VectorLike, cos_theta,
|
||||
};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
||||
Interaction, InteractionData, InteractionTrait, MediumInteraction, SimpleInteraction,
|
||||
SurfaceInteraction,
|
||||
};
|
||||
use crate::core::medium::MediumInterface;
|
||||
use crate::core::pbrt::{Float, PI};
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::images::Image;
|
||||
use crate::lights::*;
|
||||
use crate::spectra::{
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum,
|
||||
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum,
|
||||
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::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
use crate::{Float, PI};
|
||||
use bitflags::bitflags;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use crate::lights::diffuse::DiffuseAreaLight;
|
||||
use crate::lights::infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
|
||||
|
||||
use crate::lights::sampler::{LightSampler, LightSamplerTrait};
|
||||
|
||||
static SPECTRUM_CACHE: OnceLock<InternCache<DenselySampledSpectrum>> = OnceLock::new();
|
||||
|
||||
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
|
||||
SPECTRUM_CACHE.get_or_init(InternCache::new)
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LightType: u32 {
|
||||
const DeltaPosition = 1;
|
||||
|
|
@ -52,15 +44,37 @@ impl LightType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LightLeSample {
|
||||
l: SampledSpectrum,
|
||||
ray: Ray,
|
||||
intr: Option<Interaction>,
|
||||
pdf_pos: Float,
|
||||
pdf_dir: Float,
|
||||
pub l: SampledSpectrum,
|
||||
pub ray: Ray,
|
||||
pub intr: *const InteractionData,
|
||||
pub pdf_pos: 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)]
|
||||
pub struct LightSampleContext {
|
||||
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)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LightBaseData {
|
||||
pub struct LightBase {
|
||||
pub render_from_light: Transform,
|
||||
pub light_type: u32,
|
||||
pub mi_inside: i32,
|
||||
pub mi_outside: i32,
|
||||
pub medium_interface: MediumInterface,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl LightBase {
|
||||
pub fn new(
|
||||
light_type: LightType,
|
||||
render_from_light: &TransformGeneric<Float>,
|
||||
render_from_light: &Transform,
|
||||
medium_interface: &MediumInterface,
|
||||
) -> 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(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
|
|
@ -181,24 +184,20 @@ impl LightBase {
|
|||
pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
SampledSpectrum::default()
|
||||
}
|
||||
|
||||
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
|
||||
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
|
||||
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
|
||||
cache.lookup(dense_spectrum)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LightBounds {
|
||||
bounds: Bounds3f,
|
||||
phi: Float,
|
||||
w: Vector3f,
|
||||
cos_theta_o: Float,
|
||||
cos_theta_e: Float,
|
||||
two_sided: bool,
|
||||
pub bounds: Bounds3f,
|
||||
pub phi: Float,
|
||||
pub w: Vector3f,
|
||||
pub cos_theta_o: Float,
|
||||
pub cos_theta_e: Float,
|
||||
pub two_sided: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl LightBounds {
|
||||
pub fn new(
|
||||
bounds: &Bounds3f,
|
||||
|
|
@ -217,7 +216,9 @@ impl LightBounds {
|
|||
two_sided,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LightBounds {
|
||||
pub fn centroid(&self) -> Point3f {
|
||||
self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
|
||||
}
|
||||
|
|
@ -301,9 +302,9 @@ impl LightBounds {
|
|||
}
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
||||
pub trait LightTrait {
|
||||
fn base(&self) -> &LightBase;
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
|
|
@ -311,7 +312,9 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
|||
lambda: &SampledWavelengths,
|
||||
allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample>;
|
||||
|
||||
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float;
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
p: Point3f,
|
||||
|
|
@ -320,16 +323,26 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug {
|
|||
w: Vector3f,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum;
|
||||
|
||||
fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum;
|
||||
fn preprocess(&mut self, scene_bounds: &Bounds3f);
|
||||
fn bounds(&self) -> Option<LightBounds>;
|
||||
|
||||
fn light_type(&self) -> LightType {
|
||||
self.base().light_type()
|
||||
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)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Light {
|
||||
DiffuseArea(DiffuseAreaLight),
|
||||
|
|
@ -342,548 +355,3 @@ pub enum Light {
|
|||
Projection(ProjectionLight),
|
||||
Spot(SpotLight),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DistantLightData {
|
||||
pub base: LightBaseData,
|
||||
pub lemit_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
}
|
||||
|
||||
impl DistantLightData {
|
||||
pub fn sample_li(
|
||||
&self,
|
||||
ctx_p: Point3f,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> (SampledSpectrum, Vector3f, Float, Point3f) {
|
||||
let wi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let p_outside = ctx_p + wi * 2. * self.scene_radius;
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
||||
let li = self.scale * spectrum.sample(lambda);
|
||||
(li, wi, 1.0, p_outside)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for DistantLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
self.scale * self.lemit.sample(&lambda) * PI * self.scene_radius.sqrt()
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let wi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let p_outside = ctx.p() + wi * 2. * self.scene_radius;
|
||||
|
||||
let li = self.scale * self.lemit.sample(lambda);
|
||||
let intr = SimpleInteraction::new(
|
||||
Point3fi::new_from_point(p_outside),
|
||||
0.0,
|
||||
Some(self.base.medium_interface.clone()),
|
||||
);
|
||||
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn preprocess(&mut self, scene_bounds: &Bounds3f) {
|
||||
let (center, radius) = scene_bounds.bounding_sphere();
|
||||
self.scene_center = center;
|
||||
self.scene_radius = radius;
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoniometricLight {
|
||||
pub base: LightBase,
|
||||
iemit: Arc<DenselySampledSpectrum>,
|
||||
scale: Float,
|
||||
image: Image,
|
||||
distrib: PiecewiseConstant2D,
|
||||
}
|
||||
|
||||
impl GoniometricLight {
|
||||
pub fn new(
|
||||
render_from_light: &TransformGeneric<Float>,
|
||||
medium_interface: &MediumInterface,
|
||||
iemit: Spectrum,
|
||||
scale: Float,
|
||||
image: Image,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
render_from_light,
|
||||
medium_interface,
|
||||
);
|
||||
|
||||
let i_interned = LightBase::lookup_spectrum(&iemit);
|
||||
let d = image.get_sampling_distribution_uniform();
|
||||
let distrib = PiecewiseConstant2D::new_with_data(&d);
|
||||
Self {
|
||||
base,
|
||||
iemit: i_interned,
|
||||
scale,
|
||||
image,
|
||||
distrib,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let uv = equal_area_sphere_to_square(w);
|
||||
self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for GoniometricLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum_y = 0.;
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
sum_y += self.image.get_channel(Point2i::new(x, y), 0);
|
||||
}
|
||||
}
|
||||
self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y
|
||||
/ (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
_lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PointLightData {
|
||||
pub base: LightBaseData,
|
||||
pub i_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
}
|
||||
|
||||
impl PointLightData {
|
||||
pub fn sample_li(
|
||||
&self,
|
||||
ctx_p: Point3f,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> (SampledSpectrum, Vector3f, Float, Point3fi) {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx_p).normalize();
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.i_coeffs);
|
||||
let li = self.scale * spectrum.sample(lambda) / p.distance_squared(ctx_p);
|
||||
(li, wi, 1.0, pi)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for PointLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p());
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
4. * PI * self.scale * self.i.sample(&lambda)
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
let p = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_point(Point3f::new(0., 0., 0.));
|
||||
let phi = 4. * PI * self.scale * self.i.max_value();
|
||||
Some(LightBounds::new(
|
||||
&Bounds3f::from_points(p, p),
|
||||
Vector3f::new(0., 0., 1.),
|
||||
phi,
|
||||
PI.cos(),
|
||||
(PI / 2.).cos(),
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ProjectionLightPOD {
|
||||
pub base: LightBasePOD,
|
||||
pub scale: Float,
|
||||
pub hither: Float,
|
||||
pub screen_from_light: Transform,
|
||||
pub screen_bounds: Bounds2f,
|
||||
pub image_id: u32,
|
||||
}
|
||||
|
||||
impl ProjectionLight {
|
||||
pub fn new(
|
||||
render_from_light: TransformGeneric<Float>,
|
||||
medium_interface: MediumInterface,
|
||||
image: Image,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
scale: Float,
|
||||
fov: Float,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
&render_from_light,
|
||||
&medium_interface,
|
||||
);
|
||||
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
|
||||
let screen_bounds = if aspect > 1. {
|
||||
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
|
||||
} else {
|
||||
Bounds2f::from_points(
|
||||
Point2f::new(-1., 1. / aspect),
|
||||
Point2f::new(1., 1. / aspect),
|
||||
)
|
||||
};
|
||||
|
||||
let hither = 1e-3;
|
||||
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
|
||||
let light_from_screen = screen_from_light.inverse();
|
||||
let opposite = (radians(fov) / 2.).tan();
|
||||
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
||||
let a = 4. * square(opposite) * aspect_ratio;
|
||||
let dwda = |p: Point2f| {
|
||||
let w =
|
||||
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
|
||||
cos_theta(w.normalize()).powi(3)
|
||||
};
|
||||
|
||||
let d = image.get_sampling_distribution(dwda, screen_bounds);
|
||||
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
|
||||
|
||||
Self {
|
||||
base,
|
||||
image,
|
||||
image_color_space,
|
||||
screen_bounds,
|
||||
screen_from_light,
|
||||
light_from_screen,
|
||||
scale,
|
||||
hither,
|
||||
a,
|
||||
distrib,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
if w.z() < self.hither {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
let ps = self.screen_from_light.apply_to_point(w.into());
|
||||
if !self.screen_bounds.contains(Point2f::new(ps.x(), ps.y())) {
|
||||
return SampledSpectrum::new(0.);
|
||||
}
|
||||
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.lookup_nearest_channel(uv, c);
|
||||
}
|
||||
let s = RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
|
||||
self.scale * s.sample(&lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for ProjectionLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
let mut sum = SampledSpectrum::new(0.);
|
||||
for y in 0..self.image.resolution.y() {
|
||||
for x in 0..self.image.resolution.x() {
|
||||
let ps = self.screen_bounds.lerp(Point2f::new(
|
||||
(x as Float + 0.5) / self.image.resolution.x() as Float,
|
||||
(y as Float + 0.5) / self.image.resolution.y() as Float,
|
||||
));
|
||||
let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
|
||||
ps.x(),
|
||||
ps.y(),
|
||||
0.,
|
||||
)));
|
||||
let w = w_raw.normalize();
|
||||
let dwda = cos_theta(w).powi(3);
|
||||
let mut rgb = RGB::default();
|
||||
for c in 0..3 {
|
||||
rgb[c] = self.image.get_channel(Point2i::new(x, y), c);
|
||||
}
|
||||
|
||||
let s = RGBIlluminantSpectrum::new(
|
||||
self.image_color_space.as_ref(),
|
||||
RGB::clamp_zero(rgb),
|
||||
);
|
||||
sum += s.sample(&lambda) * dwda;
|
||||
}
|
||||
}
|
||||
self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
_lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
todo!()
|
||||
}
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
todo!()
|
||||
}
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SpotLightData {
|
||||
pub base: LightBaseData,
|
||||
pub iemit_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
pub cos_falloff_start: Float,
|
||||
pub cos_falloff_end: Float,
|
||||
}
|
||||
|
||||
impl SpotLightData {
|
||||
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let cos_theta = w.z(); // assuming normalized in light space
|
||||
let falloff = crate::utils::math::smooth_step(
|
||||
cos_theta,
|
||||
self.cos_falloff_end,
|
||||
self.cos_falloff_start,
|
||||
);
|
||||
let spectrum = DenselySampledSpectrum::from_array(&self.iemit_coeffs);
|
||||
falloff * self.scale * spectrum.sample(lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightTrait for SpotLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
_u: Point2f,
|
||||
lambda: &SampledWavelengths,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Option<LightLiSample> {
|
||||
let pi = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_interval(&Point3fi::default());
|
||||
let p: Point3f = pi.into();
|
||||
let wi = (p - ctx.p()).normalize();
|
||||
let w_light = self.base.render_from_light.apply_inverse_vector(-wi);
|
||||
let li = self.i(w_light, *lambda) / p.distance_squared(ctx.p());
|
||||
|
||||
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
|
||||
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
|
||||
}
|
||||
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
self.scale
|
||||
* self.iemit.sample(&lambda)
|
||||
* 2.
|
||||
* PI
|
||||
* ((1. - self.cos_fallof_start) + (self.cos_fallof_start - self.cos_fallof_end) / 2.)
|
||||
}
|
||||
|
||||
fn pdf_li(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
_wi: Vector3f,
|
||||
_allow_incomplete_pdf: bool,
|
||||
) -> Float {
|
||||
0.
|
||||
}
|
||||
|
||||
fn l(
|
||||
&self,
|
||||
_p: Point3f,
|
||||
_n: Normal3f,
|
||||
_uv: Point2f,
|
||||
_w: Vector3f,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
let p = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_point(Point3f::default());
|
||||
let w = self
|
||||
.base
|
||||
.render_from_light
|
||||
.apply_to_vector(Vector3f::new(0., 0., 1.))
|
||||
.normalize();
|
||||
let phi = self.scale * self.iemit.max_value() * 4. * PI;
|
||||
let cos_theta_e = (self.cos_fallof_end.acos() - self.cos_fallof_start.acos()).cos();
|
||||
Some(LightBounds::new(
|
||||
&Bounds3f::from_points(p, p),
|
||||
w,
|
||||
phi,
|
||||
self.cos_fallof_start,
|
||||
cos_theta_e,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use bumpalo::Bump;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::ops::Deref;
|
||||
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::interaction::InteractionTrait;
|
||||
use crate::core::interaction::{Interaction, Shadinggeom, SurfaceInteraction};
|
||||
use crate::core::interaction::{Interaction, ShadingGeom, SurfaceInteraction};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::core::texture::{
|
||||
FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator,
|
||||
GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator,
|
||||
};
|
||||
use crate::image::{Image, WrapMode, WrapMode2D};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait};
|
||||
use crate::images::{Image, WrapMode, WrapMode2D};
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::hash::hash_float;
|
||||
use crate::utils::math::clamp;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct MaterialEvalContext {
|
||||
pub texture: TextureEvalContext,
|
||||
pub wo: Vector3f,
|
||||
|
|
@ -47,20 +49,21 @@ impl From<&SurfaceInteraction> for MaterialEvalContext {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NormalBumpEvalContext {
|
||||
p: Point3f,
|
||||
uv: Point2f,
|
||||
n: Normal3f,
|
||||
shading: Shadinggeom,
|
||||
dpdx: Vector3f,
|
||||
dpdy: Vector3f,
|
||||
pub p: Point3f,
|
||||
pub uv: Point2f,
|
||||
pub n: Normal3f,
|
||||
pub shading: ShadingGeom,
|
||||
pub dpdx: Vector3f,
|
||||
pub dpdy: Vector3f,
|
||||
// All 0
|
||||
dudx: Float,
|
||||
dudy: Float,
|
||||
dvdx: Float,
|
||||
dvdy: Float,
|
||||
face_index: usize,
|
||||
pub dudx: Float,
|
||||
pub dudy: Float,
|
||||
pub dvdx: Float,
|
||||
pub dvdy: Float,
|
||||
pub face_index: usize,
|
||||
}
|
||||
|
||||
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>(
|
||||
tex_eval: &T,
|
||||
displacement: &FloatTexture,
|
||||
displacement: &GPUFloatTexture,
|
||||
ctx: &NormalBumpEvalContext,
|
||||
) -> (Vector3f, Vector3f) {
|
||||
debug_assert!(tex_eval.can_evaluate(&[displacement], &[]));
|
||||
|
|
@ -168,12 +171,12 @@ pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
|
|||
) -> Option<BSSRDF>;
|
||||
|
||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
|
||||
fn get_normal_map(&self) -> Option<Arc<Image>>;
|
||||
fn get_displacement(&self) -> Option<FloatTexture>;
|
||||
fn get_normal_map(&self) -> *const Image;
|
||||
fn get_displacement(&self) -> Option<GPUFloatTexture>;
|
||||
fn has_surface_scattering(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[enum_dispatch(MaterialTrait)]
|
||||
pub enum Material {
|
||||
CoatedDiffuse(CoatedDiffuseMaterial),
|
||||
|
|
@ -191,32 +194,33 @@ pub enum Material {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CoatedDiffuseMaterial {
|
||||
displacement: FloatTexture,
|
||||
normal_map: Option<Arc<Image>>,
|
||||
reflectance: SpectrumTexture,
|
||||
albedo: SpectrumTexture,
|
||||
u_roughness: FloatTexture,
|
||||
v_roughness: FloatTexture,
|
||||
thickness: FloatTexture,
|
||||
g: FloatTexture,
|
||||
eta: Spectrum,
|
||||
remap_roughness: bool,
|
||||
max_depth: usize,
|
||||
n_samples: usize,
|
||||
pub displacement: GPUFloatTexture,
|
||||
pub normal_map: *const Image,
|
||||
pub reflectance: GPUSpectrumTexture,
|
||||
pub albedo: GPUSpectrumTexture,
|
||||
pub u_roughness: GPUFloatTexture,
|
||||
pub v_roughness: GPUFloatTexture,
|
||||
pub thickness: GPUFloatTexture,
|
||||
pub g: GPUFloatTexture,
|
||||
pub eta: Spectrum,
|
||||
pub remap_roughness: bool,
|
||||
pub max_depth: usize,
|
||||
pub n_samples: usize,
|
||||
}
|
||||
|
||||
impl CoatedDiffuseMaterial {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(
|
||||
reflectance: SpectrumTexture,
|
||||
u_roughness: FloatTexture,
|
||||
v_roughness: FloatTexture,
|
||||
thickness: FloatTexture,
|
||||
albedo: SpectrumTexture,
|
||||
g: FloatTexture,
|
||||
reflectance: GPUSpectrumTexture,
|
||||
u_roughness: GPUFloatTexture,
|
||||
v_roughness: GPUFloatTexture,
|
||||
thickness: GPUFloatTexture,
|
||||
albedo: GPUSpectrumTexture,
|
||||
g: GPUFloatTexture,
|
||||
eta: Spectrum,
|
||||
displacement: FloatTexture,
|
||||
normal_map: Option<Arc<Image>>,
|
||||
displacement: GPUFloatTexture,
|
||||
normal_map: *const Image,
|
||||
remap_roughness: bool,
|
||||
max_depth: usize,
|
||||
n_samples: usize,
|
||||
|
|
@ -313,8 +317,8 @@ impl MaterialTrait for CoatedDiffuseMaterial {
|
|||
)
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||
self.normal_map.clone()
|
||||
fn get_normal_map(&self) -> *const Image {
|
||||
self.normal_map
|
||||
}
|
||||
|
||||
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 {
|
||||
displacement: FloatTexture,
|
||||
normal_map: Option<Arc<Image>>,
|
||||
normal_map: *const Image,
|
||||
interface_uroughness: FloatTexture,
|
||||
interface_vroughness: FloatTexture,
|
||||
thickness: FloatTexture,
|
||||
|
|
@ -348,6 +353,7 @@ pub struct CoatedConductorMaterial {
|
|||
|
||||
impl CoatedConductorMaterial {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(
|
||||
displacement: FloatTexture,
|
||||
normal_map: Option<Arc<Image>>,
|
||||
|
|
@ -502,8 +508,8 @@ impl MaterialTrait for CoatedConductorMaterial {
|
|||
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||
self.normal_map.clone()
|
||||
fn get_normal_map(&self) -> *const Image {
|
||||
self.normal_map
|
||||
}
|
||||
|
||||
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;
|
||||
impl MaterialTrait for ConductorMaterial {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
|
|
@ -537,7 +544,7 @@ impl MaterialTrait for ConductorMaterial {
|
|||
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
|
||||
todo!()
|
||||
}
|
||||
fn get_normal_map(&self) -> Option<Arc<Image>> {
|
||||
fn get_normal_map(&self) -> *const Image {
|
||||
todo!()
|
||||
}
|
||||
fn get_displacement(&self) -> Option<FloatTexture> {
|
||||
|
|
@ -547,9 +554,11 @@ impl MaterialTrait for ConductorMaterial {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DielectricMaterial {
|
||||
normal_map: Option<Arc<Image>>,
|
||||
normal_map: *const Image,
|
||||
displacement: FloatTexture,
|
||||
u_roughness: FloatTexture,
|
||||
v_roughness: FloatTexture,
|
||||
|
|
@ -612,9 +621,11 @@ impl MaterialTrait for DielectricMaterial {
|
|||
false
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DiffuseMaterial {
|
||||
normal_map: Option<Arc<Image>>,
|
||||
normal_map: *const Image,
|
||||
displacement: FloatTexture,
|
||||
reflectance: SpectrumTexture,
|
||||
}
|
||||
|
|
@ -656,8 +667,11 @@ impl MaterialTrait for DiffuseMaterial {
|
|||
false
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DiffuseTransmissionMaterial;
|
||||
|
||||
impl MaterialTrait for DiffuseTransmissionMaterial {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
&self,
|
||||
|
|
@ -692,8 +706,11 @@ impl MaterialTrait for DiffuseTransmissionMaterial {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HairMaterial;
|
||||
|
||||
impl MaterialTrait for HairMaterial {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
&self,
|
||||
|
|
@ -726,7 +743,8 @@ impl MaterialTrait for HairMaterial {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MeasuredMaterial;
|
||||
impl MaterialTrait for MeasuredMaterial {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
|
|
@ -759,7 +777,9 @@ impl MaterialTrait for MeasuredMaterial {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SubsurfaceMaterial;
|
||||
impl MaterialTrait for SubsurfaceMaterial {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
|
|
@ -792,7 +812,9 @@ impl MaterialTrait for SubsurfaceMaterial {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ThinDielectricMaterial;
|
||||
impl MaterialTrait for ThinDielectricMaterial {
|
||||
fn get_bsdf<T: TextureEvaluator>(
|
||||
|
|
@ -824,10 +846,12 @@ impl MaterialTrait for ThinDielectricMaterial {
|
|||
todo!()
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MixMaterial {
|
||||
pub amount: FloatTexture,
|
||||
pub materials: [Box<Material>; 2],
|
||||
pub materials: [Ptr<Material>; 2],
|
||||
}
|
||||
|
||||
impl MixMaterial {
|
||||
|
|
@ -835,23 +859,19 @@ impl MixMaterial {
|
|||
&self,
|
||||
tex_eval: &T,
|
||||
ctx: &MaterialEvalContext,
|
||||
) -> &Material {
|
||||
) -> Option<&Material> {
|
||||
let amt = tex_eval.evaluate_float(&self.amount, ctx);
|
||||
|
||||
if amt <= 0.0 {
|
||||
return &self.materials[0];
|
||||
}
|
||||
if amt >= 1.0 {
|
||||
return &self.materials[1];
|
||||
}
|
||||
|
||||
let u = hash_float(&(ctx.p, ctx.wo));
|
||||
|
||||
if amt < u {
|
||||
&self.materials[0]
|
||||
let index = if amt <= 0.0 {
|
||||
0
|
||||
} else if amt >= 1.0 {
|
||||
1
|
||||
} 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,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> BSDF {
|
||||
let chosen_mat = self.choose_material(tex_eval, ctx);
|
||||
chosen_mat.get_bsdf(tex_eval, ctx, lambda)
|
||||
if let Some(mat) = self.choose_material(tex_eval, ctx) {
|
||||
mat.get_bsdf(tex_eval, ctx, lambda)
|
||||
} else {
|
||||
BSDF::empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bssrdf<T>(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use bumpalo::Bump;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -8,13 +7,14 @@ use crate::core::geometry::{
|
|||
use crate::core::pbrt::{Float, INV_4_PI, PI};
|
||||
use crate::spectra::{
|
||||
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
|
||||
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
||||
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
|
||||
};
|
||||
use crate::utils::containers::SampledGrid;
|
||||
use crate::utils::math::{clamp, square};
|
||||
use crate::utils::rng::Rng;
|
||||
use crate::utils::transform::TransformGeneric;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PhaseFunctionSample {
|
||||
pub p: Float,
|
||||
|
|
@ -35,12 +35,14 @@ pub trait PhaseFunctionTrait {
|
|||
fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[enum_dispatch(PhaseFunctionTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PhaseFunction {
|
||||
HenyeyGreenstein(HGPhaseFunction),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HGPhaseFunction {
|
||||
g: Float,
|
||||
|
|
@ -85,14 +87,19 @@ impl PhaseFunctionTrait for HGPhaseFunction {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MajorantGrid {
|
||||
pub bounds: Bounds3f,
|
||||
pub res: Point3i,
|
||||
pub voxels: Vec<Float>,
|
||||
pub voxels: *const Float,
|
||||
}
|
||||
|
||||
unsafe impl Send for MajorantGrid {}
|
||||
unsafe impl Sync for MajorantGrid {}
|
||||
|
||||
impl MajorantGrid {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
|
|
@ -100,18 +107,37 @@ impl MajorantGrid {
|
|||
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 {
|
||||
if !self.is_valid() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
|
||||
|
||||
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
||||
self.voxels[idx as usize]
|
||||
unsafe { *self.voxels.add(idx as usize) }
|
||||
} else {
|
||||
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;
|
||||
if idx >= 0 && (idx as usize) < self.voxels.len() {
|
||||
self.voxels[idx as usize] = v;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set(&self, x: i32, y: i32, z: i32, v: Float) {
|
||||
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)]
|
||||
pub struct RayMajorantSegment {
|
||||
t_min: Float,
|
||||
|
|
@ -137,6 +164,8 @@ pub struct RayMajorantSegment {
|
|||
sigma_maj: SampledSpectrum,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RayMajorantIterator {
|
||||
Homogeneous(HomogeneousMajorantIterator),
|
||||
DDA(DDAMajorantIterator),
|
||||
|
|
@ -155,6 +184,9 @@ impl Iterator for RayMajorantIterator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HomogeneousMajorantIterator {
|
||||
called: bool,
|
||||
seg: RayMajorantSegment,
|
||||
|
|
@ -186,6 +218,8 @@ impl Iterator for HomogeneousMajorantIterator {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DDAMajorantIterator {
|
||||
sigma_t: SampledSpectrum,
|
||||
t_min: Float,
|
||||
|
|
@ -197,6 +231,7 @@ pub struct DDAMajorantIterator {
|
|||
voxel_limit: [i32; 3],
|
||||
voxel: [i32; 3],
|
||||
}
|
||||
|
||||
impl DDAMajorantIterator {
|
||||
pub fn new(
|
||||
ray: &Ray,
|
||||
|
|
@ -209,7 +244,7 @@ impl DDAMajorantIterator {
|
|||
t_min,
|
||||
t_max,
|
||||
sigma_t: *sigma_t,
|
||||
grid: grid.clone(),
|
||||
grid: *grid,
|
||||
next_crossing_t: [0.0; 3],
|
||||
delta_t: [0.0; 3],
|
||||
step: [0; 3],
|
||||
|
|
@ -227,28 +262,29 @@ impl DDAMajorantIterator {
|
|||
|
||||
let p_grid_start = grid.bounds.offset(&ray.at(t_min));
|
||||
let grid_intersect = Vector3f::from(p_grid_start);
|
||||
let res = [grid.res.x, grid.res.y, grid.res.z];
|
||||
|
||||
for axis in 0..3 {
|
||||
iter.voxel[axis] = clamp(
|
||||
(grid_intersect[axis] * grid.res[axis] as Float) as i32,
|
||||
(grid_intersect[axis] * res[axis] as Float) as i32,
|
||||
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 {
|
||||
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] =
|
||||
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
||||
iter.step[axis] = 1;
|
||||
iter.voxel_limit[axis] = grid.res[axis];
|
||||
iter.voxel_limit[axis] = res[axis];
|
||||
} 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] =
|
||||
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
|
||||
iter.step[axis] = -1;
|
||||
|
|
@ -313,6 +349,8 @@ impl Iterator for DDAMajorantIterator {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MediumProperties {
|
||||
pub sigma_a: SampledSpectrum,
|
||||
pub sigma_s: SampledSpectrum,
|
||||
|
|
@ -335,7 +373,6 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
|
|||
ray: &Ray,
|
||||
t_max: Float,
|
||||
lambda: &SampledWavelengths,
|
||||
buf: &Bump,
|
||||
) -> RayMajorantIterator;
|
||||
|
||||
fn sample_t_maj<F>(
|
||||
|
|
@ -354,8 +391,7 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
|
|||
t_max *= len;
|
||||
ray.d /= len;
|
||||
|
||||
let buf = Bump::new();
|
||||
let mut iter = self.sample_ray(&ray, t_max, lambda, &buf);
|
||||
let mut iter = self.sample_ray(&ray, t_max, lambda);
|
||||
let mut t_maj = SampledSpectrum::new(1.0);
|
||||
|
||||
while let Some(seg) = iter.next() {
|
||||
|
|
@ -463,7 +499,6 @@ impl MediumTrait for HomogeneousMedium {
|
|||
_ray: &Ray,
|
||||
t_max: Float,
|
||||
lambda: &SampledWavelengths,
|
||||
_scratch: &Bump,
|
||||
) -> RayMajorantIterator {
|
||||
let sigma_a = self.sigma_a_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 {
|
||||
bounds: Bounds3f,
|
||||
render_from_medium: TransformGeneric<Float>,
|
||||
|
|
@ -483,7 +519,7 @@ pub struct GridMedium {
|
|||
sigma_s_spec: DenselySampledSpectrum,
|
||||
density_grid: SampledGrid<Float>,
|
||||
phase: HGPhaseFunction,
|
||||
temperature_grid: Option<SampledGrid<Float>>,
|
||||
temperature_grid: SampledGrid<Float>,
|
||||
le_spec: DenselySampledSpectrum,
|
||||
le_scale: SampledGrid<Float>,
|
||||
is_emissive: bool,
|
||||
|
|
@ -492,15 +528,16 @@ pub struct GridMedium {
|
|||
|
||||
impl GridMedium {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(
|
||||
bounds: &Bounds3f,
|
||||
render_from_medium: &TransformGeneric<Float>,
|
||||
render_from_medium: &Transform,
|
||||
sigma_a: &Spectrum,
|
||||
sigma_s: &Spectrum,
|
||||
sigma_scale: Float,
|
||||
g: Float,
|
||||
density_grid: SampledGrid<Float>,
|
||||
temperature_grid: Option<SampledGrid<Float>>,
|
||||
temperature_grid: SampledGrid<Float>,
|
||||
le: &Spectrum,
|
||||
le_scale: SampledGrid<Float>,
|
||||
) -> Self {
|
||||
|
|
@ -588,7 +625,6 @@ impl MediumTrait for GridMedium {
|
|||
ray: &Ray,
|
||||
t_max: Float,
|
||||
lambda: &SampledWavelengths,
|
||||
_buf: &Bump,
|
||||
) -> RayMajorantIterator {
|
||||
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 {
|
||||
bounds: Bounds3f,
|
||||
render_from_medium: TransformGeneric<Float>,
|
||||
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
||||
le_scale: Float,
|
||||
render_from_medium: Transform,
|
||||
phase: HGPhaseFunction,
|
||||
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
||||
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
||||
le_scale: Float,
|
||||
sigma_scale: Float,
|
||||
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
le_grid: SampledGrid<RGBIlluminantSpectrum>,
|
||||
majorant_grid: MajorantGrid,
|
||||
}
|
||||
|
||||
impl RGBGridMedium {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(
|
||||
bounds: &Bounds3f,
|
||||
render_from_medium: &TransformGeneric<Float>,
|
||||
g: Float,
|
||||
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
||||
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
|
||||
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
|
||||
sigma_scale: Float,
|
||||
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
|
||||
le_grid: SampledGrid<RGBIlluminantSpectrum>,
|
||||
le_scale: Float,
|
||||
) -> Self {
|
||||
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
|
||||
|
|
@ -679,31 +717,29 @@ impl RGBGridMedium {
|
|||
|
||||
impl MediumTrait for RGBGridMedium {
|
||||
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 {
|
||||
let p_transform = self.render_from_medium.apply_inverse(p);
|
||||
let p = Point3f::from(self.bounds.offset(&p_transform));
|
||||
let convert = |s: &RGBUnboundedSpectrum| s.sample(lambda);
|
||||
let sigma_a = self.sigma_scale
|
||||
* self
|
||||
.sigma_a_grid
|
||||
.as_ref()
|
||||
.map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert));
|
||||
let sigma_a = if self.sigma_a_grid.is_valid() {
|
||||
self.sigma_scale * self.sigma_a_grid.lookup_convert(p, convert)
|
||||
} else {
|
||||
SampledSpectrum::new(1.0) * self.sigma_scale // Or 0.0? Check PBRT logic
|
||||
};
|
||||
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
|
||||
* self
|
||||
.sigma_s_grid
|
||||
.as_ref()
|
||||
.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));
|
||||
let le = if self.le_grid.is_valid() && self.le_scale > 0.0 {
|
||||
self.le_grid.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale
|
||||
} else {
|
||||
SampledSpectrum::new(0.0)
|
||||
};
|
||||
|
||||
MediumProperties {
|
||||
sigma_a,
|
||||
|
|
@ -718,7 +754,6 @@ impl MediumTrait for RGBGridMedium {
|
|||
ray: &Ray,
|
||||
t_max: Float,
|
||||
_lambda: &SampledWavelengths,
|
||||
_buf: &Bump,
|
||||
) -> RayMajorantIterator {
|
||||
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.z() < 0.0 { 1 } else { 0 },
|
||||
];
|
||||
|
||||
let (t_min, t_max) =
|
||||
match self
|
||||
.bounds
|
||||
|
|
@ -763,7 +799,6 @@ impl MediumTrait for CloudMedium {
|
|||
_ray: &Ray,
|
||||
_t_max: Float,
|
||||
_lambda: &SampledWavelengths,
|
||||
_buf: &Bump,
|
||||
) -> RayMajorantIterator {
|
||||
todo!()
|
||||
}
|
||||
|
|
@ -782,28 +817,43 @@ impl MediumTrait for NanoVDBMedium {
|
|||
_ray: &Ray,
|
||||
_t_max: Float,
|
||||
_lambda: &SampledWavelengths,
|
||||
_buf: &Bump,
|
||||
) -> RayMajorantIterator {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MediumInterface {
|
||||
pub inside: Option<Arc<Medium>>,
|
||||
pub outside: Option<Arc<Medium>>,
|
||||
pub inside: *const Medium,
|
||||
pub outside: *const Medium,
|
||||
}
|
||||
|
||||
impl MediumInterface {
|
||||
pub fn new(inside: Option<Arc<Medium>>, outside: Option<Arc<Medium>>) -> Self {
|
||||
Self { inside, outside }
|
||||
}
|
||||
unsafe impl Send for MediumInterface {}
|
||||
unsafe impl Sync for MediumInterface {}
|
||||
|
||||
pub fn is_medium_transition(&self) -> bool {
|
||||
match (&self.inside, &self.outside) {
|
||||
(Some(inside), Some(outside)) => !Arc::ptr_eq(inside, outside),
|
||||
(None, None) => false,
|
||||
_ => true,
|
||||
impl Default for MediumInterface {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inside: core::ptr::null(),
|
||||
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 bxdf;
|
||||
pub mod camera;
|
||||
pub mod color;
|
||||
pub mod film;
|
||||
pub mod filter;
|
||||
pub mod geometry;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::core::aggregates::LinearBVHNode;
|
||||
use crate::core::geometry::{Bounds3f, Ray};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::light::Light;
|
||||
use crate::core::material::Material;
|
||||
use crate::core::medium::{Medium, MediumInterface};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::texture::{FloatTextureTrait, TextureEvalContext};
|
||||
use crate::lights::Light;
|
||||
use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
|
||||
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
|
||||
use crate::utils::hash::hash_float;
|
||||
use crate::utils::transform::{AnimatedTransform, TransformGeneric};
|
||||
|
|
@ -14,21 +14,25 @@ use enum_dispatch::enum_dispatch;
|
|||
use std::sync::Arc;
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait PrimitiveTrait: Send + Sync + std::fmt::Debug {
|
||||
pub trait PrimitiveTrait {
|
||||
fn bounds(&self) -> Bounds3f;
|
||||
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
|
||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GeometricPrimitive {
|
||||
shape: Arc<Shape>,
|
||||
material: Arc<Material>,
|
||||
area_light: Arc<Light>,
|
||||
shape: *const Shape,
|
||||
material: *const Material,
|
||||
area_light: *const Light,
|
||||
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 {
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
self.shape.bounds()
|
||||
|
|
@ -80,8 +84,9 @@ impl PrimitiveTrait for GeometricPrimitive {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimplePrimitiv {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SimplePrimitive {
|
||||
shape: Arc<Shape>,
|
||||
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::{
|
||||
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
|
||||
};
|
||||
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::spectra::color::ColorEncoding;
|
||||
use crate::spectra::{
|
||||
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths, Spectrum, SpectrumTrait,
|
||||
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths,
|
||||
};
|
||||
use crate::textures::*;
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::math::square;
|
||||
use crate::{Float, INV_2_PI, INV_PI, PI};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct TexCoord2D {
|
||||
pub st: Point2f,
|
||||
pub dsdx: Float,
|
||||
|
|
@ -24,7 +25,7 @@ pub struct TexCoord2D {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub enum TextureMapping2D {
|
||||
UV(UVMapping),
|
||||
Spherical(SphericalMapping),
|
||||
|
|
@ -43,12 +44,13 @@ impl TextureMapping2D {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct UVMapping {
|
||||
su: Float,
|
||||
sv: Float,
|
||||
du: Float,
|
||||
dv: Float,
|
||||
pub su: Float,
|
||||
pub sv: Float,
|
||||
pub du: Float,
|
||||
pub dv: Float,
|
||||
}
|
||||
|
||||
impl Default for UVMapping {
|
||||
|
|
@ -83,7 +85,8 @@ impl UVMapping {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct SphericalMapping {
|
||||
texture_from_render: Transform,
|
||||
}
|
||||
|
|
@ -124,7 +127,8 @@ impl SphericalMapping {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct CylindricalMapping {
|
||||
texture_from_render: Transform,
|
||||
}
|
||||
|
|
@ -158,7 +162,8 @@ impl CylindricalMapping {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct PlanarMapping {
|
||||
texture_from_render: Transform,
|
||||
vs: Vector3f,
|
||||
|
|
@ -203,6 +208,8 @@ impl PlanarMapping {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct TexCoord3D {
|
||||
pub p: Point3f,
|
||||
pub dpdx: Vector3f,
|
||||
|
|
@ -213,7 +220,8 @@ pub trait TextureMapping3DTrait {
|
|||
fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub enum TextureMapping3D {
|
||||
PointTransform(PointTransformMapping),
|
||||
}
|
||||
|
|
@ -226,15 +234,17 @@ impl TextureMapping3D {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct PointTransformMapping {
|
||||
texture_from_render: Transform,
|
||||
pub texture_from_render: Transform,
|
||||
}
|
||||
|
||||
impl PointTransformMapping {
|
||||
pub fn new(texture_from_render: &Transform) -> Self {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(texture_from_render: Transform) -> 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 p: Point3f,
|
||||
pub dpdx: Vector3f,
|
||||
|
|
@ -325,10 +336,11 @@ impl From<&Interaction> for TextureEvalContext {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum GPUFloatTexture {
|
||||
Constant(FloatConstantTexture),
|
||||
DirectionMix(FloatDirectionMixTexture),
|
||||
Scaled(FloatScaledTexture),
|
||||
DirectionMix(GPUFloatDirectionMixTexture),
|
||||
Scaled(GPUFloatScaledTexture),
|
||||
Bilerp(FloatBilerpTexture),
|
||||
Checkerboard(FloatCheckerboardTexture),
|
||||
Dots(FloatDotsTexture),
|
||||
|
|
@ -359,6 +371,7 @@ impl GPUFloatTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum SpectrumType {
|
||||
Illuminant,
|
||||
|
|
@ -367,14 +380,16 @@ pub enum SpectrumType {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[enum_dispatch]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum GPUSpectrumTexture {
|
||||
Constant(SpectrumConstantTexture),
|
||||
Bilerp(SpectrumBilerpTexture),
|
||||
Checkerboard(SpectrumCheckerboardTexture),
|
||||
Marble(MarbleTexture),
|
||||
DirectionMix(SpectrumDirectionMixTexture),
|
||||
DirectionMix(GPUSpectrumDirectionMixTexture),
|
||||
Dots(SpectrumDotsTexture),
|
||||
Scaled(SpectrumScaledTexture),
|
||||
Scaled(GPUSpectrumScaledTexture),
|
||||
Image(GPUSpectrumImageTexture),
|
||||
Ptex(GPUSpectrumPtexTexture),
|
||||
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 mitchell;
|
||||
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 crate::camera::{Camera, CameraTrait};
|
||||
use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
|
||||
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::interaction::{
|
||||
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::primitive::{Primitive, PrimitiveTrait};
|
||||
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
|
||||
use crate::lights::sampler::LightSamplerTrait;
|
||||
use crate::lights::sampler::{LightSampler, UniformLightSampler};
|
||||
use crate::lights::{Light, LightSampleContext, LightTrait, sampler::LightSamplerTrait};
|
||||
use crate::shapes::ShapeIntersection;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
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::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::texture::GpuFloatTextureHandle;
|
||||
// use crate::shapes::GpuShapeHandle;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::core::texture::{GPUFloatTexture, TextureEvalContext, UniversalTextureEvaluator};
|
||||
use crate::images::Image;
|
||||
use crate::shapes::{Shape, ShapeSampleContext};
|
||||
use crate::spectra::*;
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::hash::hash_float;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DiffuseAreaLight {
|
||||
pub lemit_coeffs: [Float; 32],
|
||||
pub scale: Float,
|
||||
pub base: LightBase,
|
||||
pub shape: *const Shape,
|
||||
pub alpha: *const GPUFloatTexture,
|
||||
pub area: Float,
|
||||
pub two_sided: bool,
|
||||
pub image_id: i32,
|
||||
pub shape_id: u32,
|
||||
pub lemit: DenselySampledSpectrum,
|
||||
pub scale: Float,
|
||||
pub image: *const Image,
|
||||
pub image_color_space: RGBColorSpace,
|
||||
}
|
||||
|
||||
impl LightTrait for DiffuseAreaLight {
|
||||
fn l(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
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 {
|
||||
return SampledSpectrum::new(0.0);
|
||||
}
|
||||
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
|
||||
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::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,
|
||||
};
|
||||
use crate::images::{PixelFormat, WrapMode};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct InfiniteUniformLight {
|
||||
base: LightBaseData,
|
||||
lemit: u32,
|
||||
scale: Float,
|
||||
scene_center: Point3f,
|
||||
scene_radius: Float,
|
||||
pub base: LightBase,
|
||||
pub lemit: u32,
|
||||
pub scale: Float,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl InfiniteUniformLight {
|
||||
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
|
||||
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(
|
||||
&self,
|
||||
_ctx: &LightSampleContext,
|
||||
|
|
@ -119,6 +104,9 @@ impl LightTrait for InfiniteUniformLight {
|
|||
fn bounds(&self) -> Option<LightBounds> {
|
||||
todo!()
|
||||
}
|
||||
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
|
||||
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -133,6 +121,7 @@ pub struct InfiniteImageLight {
|
|||
compensated_distrib: PiecewiseConstant2D,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl InfiniteImageLight {
|
||||
pub fn new(
|
||||
render_from_light: TransformGeneric<Float>,
|
||||
|
|
@ -213,6 +202,7 @@ impl LightTrait for InfiniteImageLight {
|
|||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
|
|
@ -245,30 +235,6 @@ impl LightTrait for InfiniteImageLight {
|
|||
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 {
|
||||
let w_light = self.base.render_from_light.apply_inverse_vector(wi);
|
||||
let uv = equal_area_sphere_to_square(w_light);
|
||||
|
|
@ -301,22 +267,50 @@ impl LightTrait for InfiniteImageLight {
|
|||
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) {
|
||||
let (scene_center, scene_radius) = scene_bounds.bounding_sphere();
|
||||
self.scene_center = scene_center;
|
||||
self.scene_radius = scene_radius;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct InfinitePortalLight {
|
||||
pub base: LightBaseData,
|
||||
pub base: LightBase,
|
||||
pub image: Image,
|
||||
pub image_color_space: Arc<RGBColorSpace>,
|
||||
pub image_color_space: RGBColorSpace,
|
||||
pub scale: Float,
|
||||
pub filename: String,
|
||||
pub portal: [Point3f; 4],
|
||||
|
|
@ -326,10 +320,8 @@ pub struct InfinitePortalLight {
|
|||
pub scene_radius: Float,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
impl InfinitePortalLight {
|
||||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
pub fn new(
|
||||
render_from_light: TransformGeneric<Float>,
|
||||
equal_area_image: &Image,
|
||||
|
|
@ -520,6 +512,7 @@ impl LightTrait for InfinitePortalLight {
|
|||
fn base(&self) -> &LightBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn sample_li(
|
||||
&self,
|
||||
ctx: &LightSampleContext,
|
||||
|
|
@ -541,10 +534,6 @@ impl LightTrait for InfinitePortalLight {
|
|||
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 {
|
||||
let Some((uv, duv_dw)) = self.image_from_render(wi) else {
|
||||
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) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
fn bounds(&self) -> Option<LightBounds> {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,15 @@
|
|||
pub mod diffuse;
|
||||
pub mod distant;
|
||||
pub mod goniometric;
|
||||
pub mod infinite;
|
||||
pub mod point;
|
||||
pub mod projection;
|
||||
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::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike};
|
||||
use crate::core::geometry::{DirectionCone, Normal};
|
||||
use crate::utils::math::{clamp, lerp, sample_discrete};
|
||||
use std::collections::HashMap;
|
||||
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::{Float, ONE_MINUS_EPSILON, PI};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
#[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;
|
||||
|
||||
pub const CIE_Y_INTEGRAL: Float = 106.856895;
|
||||
|
||||
pub const CIE_SAMPLES: usize = 471;
|
||||
pub const CIE_X: [Float; CIE_SAMPLES] = [
|
||||
0.0001299000,
|
||||
|
|
@ -1425,6 +1427,112 @@ pub const CIE_Z: [Float; CIE_SAMPLES] = [
|
|||
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] = [
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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::pbrt::Float;
|
||||
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, Spectrum};
|
||||
use crate::utils::math::SquareMatrix;
|
||||
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
|
||||
use crate::utils::math::SquareMatrix3f;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
|
|
@ -10,65 +10,32 @@ use std::cmp::{Eq, PartialEq};
|
|||
use std::error::Error;
|
||||
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)]
|
||||
pub struct RGBColorSpace {
|
||||
pub r: Point2f,
|
||||
pub g: Point2f,
|
||||
pub b: Point2f,
|
||||
pub w: Point2f,
|
||||
pub illuminant: Spectrum,
|
||||
pub rgb_to_spectrum_table: Arc<RGBToSpectrumTable>,
|
||||
pub xyz_from_rgb: SquareMatrix<Float, 3>,
|
||||
pub rgb_from_xyz: SquareMatrix<Float, 3>,
|
||||
pub illuminant: DenselySampledSpectrum,
|
||||
pub rgb_to_spectrum_table: *const RGBToSpectrumTable,
|
||||
pub xyz_from_rgb: SquareMatrix3f,
|
||||
pub rgb_from_xyz: SquareMatrix3f,
|
||||
}
|
||||
|
||||
unsafe impl Send for RGBColorSpace {}
|
||||
unsafe impl Sync for 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 {
|
||||
self.xyz_from_rgb * rgb
|
||||
}
|
||||
|
|
@ -81,71 +48,13 @@ impl RGBColorSpace {
|
|||
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 {
|
||||
return SquareMatrix::default();
|
||||
return SquareMatrix3f::default();
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -154,6 +63,6 @@ impl PartialEq for RGBColorSpace {
|
|||
&& self.g == other.g
|
||||
&& self.b == other.b
|
||||
&& 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 color;
|
||||
pub mod colorspace;
|
||||
pub mod data;
|
||||
pub mod rgb;
|
||||
pub mod sampled;
|
||||
pub mod simple;
|
||||
|
||||
use crate::core::pbrt::Float;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
pub use color::{ColorEncoding, RGB, RGBSigmoidPolynomial, XYZ};
|
||||
pub use colorspace::RGBColorSpace;
|
||||
pub use data::*;
|
||||
pub use rgb::*;
|
||||
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
|
||||
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||
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::{
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace,
|
||||
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumTrait, XYZ,
|
||||
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGBColorSpace,
|
||||
SampledSpectrum, SampledWavelengths,
|
||||
};
|
||||
use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
|
||||
use crate::core::spectrum::SpectrumTrait;
|
||||
|
||||
use crate::Float;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RGBAlbedoSpectrum {
|
||||
rsp: RGBSigmoidPolynomial,
|
||||
pub rsp: RGBSigmoidPolynomial,
|
||||
}
|
||||
|
||||
impl RGBAlbedoSpectrum {
|
||||
|
|
@ -16,14 +19,6 @@ impl RGBAlbedoSpectrum {
|
|||
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 {
|
||||
|
|
@ -36,10 +31,11 @@ impl SpectrumTrait for RGBAlbedoSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct UnboundedRGBSpectrum {
|
||||
scale: Float,
|
||||
rsp: RGBSigmoidPolynomial,
|
||||
pub scale: Float,
|
||||
pub rsp: RGBSigmoidPolynomial,
|
||||
}
|
||||
|
||||
impl UnboundedRGBSpectrum {
|
||||
|
|
@ -68,38 +64,31 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct RGBIlluminantSpectrum {
|
||||
scale: Float,
|
||||
rsp: RGBSigmoidPolynomial,
|
||||
illuminant: DenselySampledSpectrum,
|
||||
pub scale: Float,
|
||||
pub rsp: RGBSigmoidPolynomial,
|
||||
pub illuminant: DenselySampledSpectrum,
|
||||
}
|
||||
|
||||
// impl RGBIlluminantSpectrum {
|
||||
// pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
||||
// let illuminant = &cs.illuminant;
|
||||
// let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
|
||||
// let m = rgb.max_component_value();
|
||||
// let scale = 2. * m;
|
||||
// let rsp = cs.to_rgb_coeffs(if scale == 1. {
|
||||
// rgb / scale
|
||||
// } else {
|
||||
// RGB::new(0., 0., 0.)
|
||||
// });
|
||||
// Self {
|
||||
// scale,
|
||||
// rsp,
|
||||
// illuminant: Some(Arc::new(densely_sampled)),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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 RGBIlluminantSpectrum {
|
||||
pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
|
||||
let illuminant = &cs.illuminant;
|
||||
let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
|
||||
let m = rgb.max_component_value();
|
||||
let scale = 2. * m;
|
||||
let rsp = cs.to_rgb_coeffs(if scale == 1. {
|
||||
rgb / scale
|
||||
} else {
|
||||
RGB::new(0., 0., 0.)
|
||||
});
|
||||
Self {
|
||||
scale,
|
||||
rsp,
|
||||
illuminant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpectrumTrait for RGBIlluminantSpectrum {
|
||||
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 {
|
||||
match &self.illuminant {
|
||||
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(),
|
||||
|
|
@ -126,8 +122,8 @@ pub struct RGBSpectrum {
|
|||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RGBUnboundedSpectrum {
|
||||
scale: Float,
|
||||
rsp: RGBSigmoidPolynomial,
|
||||
pub scale: Float,
|
||||
pub rsp: RGBSigmoidPolynomial,
|
||||
}
|
||||
|
||||
impl Default for RGBUnboundedSpectrum {
|
||||
|
|
@ -155,10 +151,6 @@ impl RGBUnboundedSpectrum {
|
|||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::core::spectrum::StandardSpectra;
|
||||
use crate::utils::math::{clamp, lerp};
|
||||
use std::ops::{
|
||||
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 N_SPECTRUM_SAMPLES: usize = 1200;
|
||||
pub const N_SPECTRUM_SAMPLES: usize = 4;
|
||||
pub const LAMBDA_MIN: i32 = 360;
|
||||
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 count = v.len().min(N_SPECTRUM_SAMPLES);
|
||||
values[..count].copy_from_slice(&v[..count]);
|
||||
for i in 0..N_SPECTRUM_SAMPLES {
|
||||
values[i] = v[i];
|
||||
}
|
||||
|
||||
Self { values }
|
||||
}
|
||||
|
||||
|
|
@ -115,8 +117,8 @@ impl SampledSpectrum {
|
|||
SampledSpectrum::from_fn(|i| if b[i] != 0. { a[i] / b[i] } else { 0. })
|
||||
}
|
||||
|
||||
pub fn y(&self, lambda: &SampledWavelengths) -> Float {
|
||||
let ys = cie_y().sample(lambda);
|
||||
pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float {
|
||||
let ys = std.cie_y().sample(lambda);
|
||||
let pdf = lambda.pdf();
|
||||
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)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct SampledWavelengths {
|
||||
pub lambda: [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 crate::core::cie::*;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::spectra::{
|
||||
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait,
|
||||
};
|
||||
use crate::utils::file::read_float_file;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use crate::Float;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||
use core::slice;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ConstantSpectrum {
|
||||
c: Float,
|
||||
pub c: Float,
|
||||
}
|
||||
|
||||
impl ConstantSpectrum {
|
||||
|
|
@ -31,90 +29,61 @@ impl SpectrumTrait for ConstantSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DenselySampledSpectrum {
|
||||
lambda_min: i32,
|
||||
lambda_max: i32,
|
||||
values: Vec<Float>,
|
||||
pub lambda_min: i32,
|
||||
pub lambda_max: i32,
|
||||
pub values: *const Float,
|
||||
}
|
||||
|
||||
unsafe impl Send for DenselySampledSpectrum {}
|
||||
unsafe impl Sync for DenselySampledSpectrum {}
|
||||
|
||||
impl DenselySampledSpectrum {
|
||||
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
|
||||
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
|
||||
Self {
|
||||
lambda_min,
|
||||
lambda_max,
|
||||
values: vec![0.0; n_values],
|
||||
#[inline(always)]
|
||||
fn as_slice(&self) -> &[Float] {
|
||||
if self.values.is_null() {
|
||||
return &[];
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
let len = (self.lambda_max - self.lambda_min + 1).max(0) as usize;
|
||||
unsafe { slice::from_raw_parts(self.values, len) }
|
||||
}
|
||||
|
||||
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let mut s = SampledSpectrum::default();
|
||||
|
||||
for i in 0..N_SPECTRUM_SAMPLES {
|
||||
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize;
|
||||
if offset >= self.values.len() {
|
||||
s[i] = 0.;
|
||||
let offset = lambda[i].round() as i32 - self.lambda_min;
|
||||
let len = (self.lambda_max - self.lambda_min + 1) as i32;
|
||||
|
||||
if offset < 0 || offset >= len {
|
||||
s[i] = 0.0;
|
||||
} else {
|
||||
s[i] = self.values[offset];
|
||||
unsafe { s[i] = *self.values.add(offset as usize) };
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
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 {
|
||||
self.values
|
||||
self.as_slice()
|
||||
.iter()
|
||||
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -128,12 +97,6 @@ impl DenselySampledSpectrum {
|
|||
}
|
||||
r
|
||||
}
|
||||
|
||||
pub fn scale(&mut self, factor: Float) {
|
||||
for v in &mut self.values {
|
||||
*v *= factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 lambdas: Vec<Float>,
|
||||
pub values: Vec<Float>,
|
||||
pub lambdas: *const Float,
|
||||
pub values: *const Float,
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
impl PiecewiseLinearSpectrum {
|
||||
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 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 })
|
||||
}
|
||||
}
|
||||
unsafe impl Send for PiecewiseLinearSpectrum {}
|
||||
unsafe impl Sync for PiecewiseLinearSpectrum {}
|
||||
|
||||
impl SpectrumTrait for PiecewiseLinearSpectrum {
|
||||
fn evaluate(&self, lambda: Float) -> Float {
|
||||
|
|
@ -274,10 +187,11 @@ impl SpectrumTrait for PiecewiseLinearSpectrum {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlackbodySpectrum {
|
||||
temperature: Float,
|
||||
normalization_factor: Float,
|
||||
pub temperature: Float,
|
||||
pub normalization_factor: Float,
|
||||
}
|
||||
|
||||
// Planck's Law
|
||||
|
|
@ -331,150 +245,3 @@ impl SpectrumTrait for BlackbodySpectrum {
|
|||
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)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct GPUFloatMixTexture {
|
||||
tex1: GPUFloatTexture,
|
||||
tex2: GPUFloatTexture,
|
||||
pub tex1: Ptr<GPUFloatTexture>,
|
||||
pub tex2: Ptr<GPUFloatTexture>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct GPUFloatDirectionMixTexture {
|
||||
tex1: GPUFloatTexture,
|
||||
tex2: GPUFloatTexture,
|
||||
dir: Vector3f,
|
||||
pub tex1: Ptr<GPUFloatTexture>,
|
||||
pub tex2: Ptr<GPUFloatTexture>,
|
||||
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 mix;
|
||||
pub mod ptex;
|
||||
pub mod scaled;
|
||||
pub mod windy;
|
||||
pub mod wrinkled;
|
||||
|
||||
|
|
@ -19,5 +20,6 @@ pub use image::*;
|
|||
pub use marble::*;
|
||||
pub use mix::*;
|
||||
pub use ptex::*;
|
||||
pub use scaled::*;
|
||||
pub use windy::*;
|
||||
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)]
|
||||
pub struct SampledGrid<T> {
|
||||
values: Vec<T>,
|
||||
nx: i32,
|
||||
ny: i32,
|
||||
nz: i32,
|
||||
pub values: *const T,
|
||||
pub nx: i32,
|
||||
pub ny: i32,
|
||||
pub nz: i32,
|
||||
}
|
||||
|
||||
unsafe impl<T: Sync> Sync for SampledGrid<T> {}
|
||||
unsafe impl<T: Send> Send for SampledGrid<T> {}
|
||||
|
||||
impl<T> SampledGrid<T> {
|
||||
pub fn new(values: Vec<T>, nx: i32, ny: i32, nz: i32) -> Self {
|
||||
assert_eq!(
|
||||
values.len(),
|
||||
(nx * ny * nz) as usize,
|
||||
"Grid dimensions do not match data size"
|
||||
);
|
||||
Self { values, nx, ny, nz }
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(slice: &[T], nx: i32, ny: i32, nz: i32) -> Self {
|
||||
assert_eq!(slice.len(), (nx * ny * nz) as usize);
|
||||
Self {
|
||||
values: slice.as_ptr(),
|
||||
nx,
|
||||
ny,
|
||||
nz,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
values: Vec::new(),
|
||||
values: core::ptr::null(),
|
||||
nx: 0,
|
||||
ny: 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 {
|
||||
self.values.len() * std::mem::size_of::<T>()
|
||||
}
|
||||
|
|
@ -212,9 +221,11 @@ impl<T> SampledGrid<T> {
|
|||
pub fn x_size(&self) -> i32 {
|
||||
self.nx
|
||||
}
|
||||
|
||||
pub fn y_size(&self) -> i32 {
|
||||
self.ny
|
||||
}
|
||||
|
||||
pub fn z_size(&self) -> i32 {
|
||||
self.nz
|
||||
}
|
||||
|
|
@ -224,7 +235,11 @@ impl<T> SampledGrid<T> {
|
|||
F: Fn(&T) -> U,
|
||||
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(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();
|
||||
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
|
||||
|
|
@ -249,6 +267,10 @@ impl<T> SampledGrid<T> {
|
|||
F: Fn(&T) -> U + Copy,
|
||||
U: Interpolatable + Default,
|
||||
{
|
||||
if !self.is_valid() {
|
||||
return U::default();
|
||||
}
|
||||
|
||||
let p_samples = Point3f::new(
|
||||
p.x() * self.nx as Float - 0.5,
|
||||
p.y() * self.ny as Float - 0.5,
|
||||
|
|
@ -298,6 +320,10 @@ impl<T> SampledGrid<T> {
|
|||
F: Fn(&T) -> U,
|
||||
U: PartialOrd + Default,
|
||||
{
|
||||
if !self.is_valid() {
|
||||
return U::default();
|
||||
}
|
||||
|
||||
let ps = [
|
||||
Point3f::new(
|
||||
bounds.p_min.x() * self.nx as Float - 0.5,
|
||||
|
|
@ -318,7 +344,6 @@ impl<T> SampledGrid<T> {
|
|||
self.nz - 1,
|
||||
));
|
||||
|
||||
// Initialize with the first voxel
|
||||
let mut max_value = self.lookup_int_convert(pi_min, &convert);
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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 num_traits::{Float as NumFloat, Num, One, Signed, Zero};
|
||||
use rayon::prelude::*;
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display};
|
||||
use std::iter::{Product, Sum};
|
||||
|
|
@ -64,16 +63,13 @@ where
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
|
||||
if coeffs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
|
||||
assert!(!coeffs.is_empty());
|
||||
let mut result = coeffs[0];
|
||||
for &c in &coeffs[1..] {
|
||||
result = t.mul_add(result, c);
|
||||
}
|
||||
Some(result)
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -913,6 +909,7 @@ pub fn multiply_generator(c: &[u32], mut a: u32) -> u32 {
|
|||
v
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NoRandomizer;
|
||||
impl NoRandomizer {
|
||||
|
|
@ -921,6 +918,7 @@ impl NoRandomizer {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BinaryPermuteScrambler {
|
||||
pub permutation: u32,
|
||||
|
|
@ -937,6 +935,7 @@ impl BinaryPermuteScrambler {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FastOwenScrambler {
|
||||
pub seed: u32,
|
||||
|
|
@ -970,6 +969,7 @@ impl FastOwenScrambler {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct OwenScrambler {
|
||||
pub seed: u32,
|
||||
|
|
@ -1096,9 +1096,10 @@ pub fn sobol_interval_to_index(m: u32, frame: u64, p: Point2i) -> u64 {
|
|||
}
|
||||
|
||||
// MATRIX STUFF (TEST THOROUGHLY)
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
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> {
|
||||
|
|
@ -1261,6 +1262,7 @@ where
|
|||
}
|
||||
|
||||
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> {
|
||||
pub fn identity() -> Self
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ pub mod interval;
|
|||
pub mod math;
|
||||
pub mod mesh;
|
||||
pub mod noise;
|
||||
pub mod ptr;
|
||||
pub mod quaternion;
|
||||
pub mod rng;
|
||||
pub mod sampling;
|
||||
|
|
@ -14,6 +15,7 @@ pub mod sobol;
|
|||
pub mod splines;
|
||||
pub mod transform;
|
||||
|
||||
pub use ptr::Ptr;
|
||||
pub use transform::{AnimatedTransform, Transform, TransformGeneric};
|
||||
|
||||
#[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::{Index, IndexMut};
|
||||
|
||||
use crate::core::geometry::{Vector3f, VectorLike};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::utils::math::{safe_asin, sinx_over_x};
|
||||
use crate::{Float, PI};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Quaternion {
|
||||
pub v: Vector3f,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ use std::iter::{Product, Sum};
|
|||
use std::ops::{Add, Div, Index, IndexMut, Mul};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::math::{radians, safe_acos, SquareMatrix};
|
||||
use super::math::{SquareMatrix, radians, safe_acos};
|
||||
use super::quaternion::Quaternion;
|
||||
use crate::core::color::{RGB, XYZ};
|
||||
use crate::core::geometry::{
|
||||
Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi,
|
||||
VectorLike,
|
||||
|
|
@ -14,10 +15,10 @@ use crate::core::geometry::{
|
|||
use crate::core::interaction::{
|
||||
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::pbrt::{gamma, Float};
|
||||
use crate::spectra::{RGB, XYZ};
|
||||
use crate::core::pbrt::{Float, gamma};
|
||||
use crate::utils::error::InversionError;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TransformGeneric<T: NumFloat> {
|
||||
m: SquareMatrix<T, 4>,
|
||||
|
|
@ -754,6 +755,7 @@ impl From<Quaternion> for TransformGeneric<Float> {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
pub struct DerivativeTerm {
|
||||
kc: Float,
|
||||
|
|
@ -777,7 +779,8 @@ impl DerivativeTerm {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Default, Clone)]
|
||||
pub struct AnimatedTransform {
|
||||
pub start_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(
|
||||
params: &ParameterDictionary,
|
||||
filter: Filter,
|
||||
sensor: Option<PixelSensor>,
|
||||
sensor: Option<&PixelSensor>,
|
||||
loc: &FileLoc,
|
||||
) -> Self {
|
||||
let x_res = params.get_one_int("xresolution", 1280);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
pub mod camera;
|
||||
pub mod color;
|
||||
pub mod film;
|
||||
pub mod filter;
|
||||
pub mod scene;
|
||||
pub mod spectrum;
|
||||
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,
|
||||
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 lights;
|
||||
pub mod spectra;
|
||||
pub mod textures;
|
||||
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 marble;
|
||||
pub mod mix;
|
||||
pub mod ptex;
|
||||
pub mod scale;
|
||||
pub mod windy;
|
||||
pub mod wrinkled;
|
||||
pub mod scaled;
|
||||
|
||||
pub use bilerp::*;
|
||||
pub use checkerboard::*;
|
||||
pub use constant::*;
|
||||
pub use dots::*;
|
||||
pub use fbm::*;
|
||||
pub use image::*;
|
||||
pub use marble::*;
|
||||
pub use mix::*;
|
||||
pub use ptex::*;
|
||||
pub use scale::*;
|
||||
pub use windy::*;
|
||||
pub use wrinkled::*;
|
||||
pub use scaled::*;
|
||||
|
|
|
|||
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);
|
||||
}
|
||||
|
||||
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>> {
|
||||
let content = fs::read_to_string(filename)?;
|
||||
|
||||
|
|
@ -45,28 +70,3 @@ pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
|
|||
|
||||
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 file;
|
||||
pub mod io;
|
||||
|
|
|
|||
Loading…
Reference in a new issue