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:
pingu 2025-12-22 22:54:49 +00:00
parent cda63e42c5
commit 4dbec9bc2c
64 changed files with 3408 additions and 2570 deletions

View file

@ -6,8 +6,7 @@ edition = "2024"
[features] [features]
default = [] default = []
use_f64 = [] use_f64 = []
cuda = ["cuda_std", "cust", "cuda_builder", "shared/cuda", ] cuda = ["cust", "cuda_builder", "shared/cuda", ]
use_nvtx = []
[dependencies] [dependencies]
anyhow = "1.0.100" anyhow = "1.0.100"
@ -35,6 +34,9 @@ kernels = { path = "kernels" }
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true } cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true }
ptex = "0.3.0"
ptex-sys = "0.3.0"
slice = "0.0.4"
[build-dependencies] [build-dependencies]
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true } spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }

View file

@ -5,7 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" } cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" }
shared = { path = "../shared" } shared = { path = "../shared", features = ["cuda"] }
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]

View file

@ -1,34 +1,31 @@
use crate::core::camera::{ use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, use crate::core::film::Film;
};
use crate::core::film::{Film, FilmTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike, Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
}; };
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::options::get_options;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::images::ImageMetadata;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc; use crate::utils::Transform;
use crate::utils::parameters::ParameterDictionary;
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::utils::transform::TransformGeneric;
use std::sync::Arc;
#[derive(Debug)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct OrthographicCamera { pub struct OrthographicCamera {
pub base: CameraBase, pub base: CameraBase,
pub screen_from_camera: TransformGeneric<Float>, pub screen_from_camera: Transform,
pub camera_from_raster: TransformGeneric<Float>, pub camera_from_raster: Transform,
pub raster_from_screen: TransformGeneric<Float>, pub raster_from_screen: Transform,
pub screen_from_raster: TransformGeneric<Float>, pub screen_from_raster: Transform,
pub lens_radius: Float, pub lens_radius: Float,
pub focal_distance: Float, pub focal_distance: Float,
pub dx_camera: Vector3f, pub dx_camera: Vector3f,
pub dy_camera: Vector3f, pub dy_camera: Vector3f,
} }
#[cfg(not(target_os = "cuda"))]
impl OrthographicCamera { impl OrthographicCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,
@ -36,21 +33,30 @@ impl OrthographicCamera {
lens_radius: Float, lens_radius: Float,
focal_distance: Float, focal_distance: Float,
) -> Self { ) -> Self {
let ndc_from_screen: TransformGeneric<Float> = TransformGeneric::scale( let ndc_from_screen: Transform = Transform::scale(
1. / (screen_window.p_max.x() - screen_window.p_min.x()), 1. / (screen_window.p_max.x() - screen_window.p_min.x()),
1. / (screen_window.p_max.y() - screen_window.p_min.y()), 1. / (screen_window.p_max.y() - screen_window.p_min.y()),
1., 1.,
) * TransformGeneric::translate( ) * Transform::translate(Vector3f::new(
Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.), -screen_window.p_min.x(),
); -screen_window.p_max.y(),
let raster_from_ndc = TransformGeneric::scale( 0.,
base.film.full_resolution().x() as Float, ));
-base.film.full_resolution().y() as Float, let film_ptr = base.film;
if film_ptr.is_null() {
panic!("Camera must have a film");
}
let film = unsafe { &*film_ptr };
let raster_from_ndc = Transform::scale(
film.full_resolution().x() as Float,
-film.full_resolution().y() as Float,
1., 1.,
); );
let raster_from_screen = raster_from_ndc * ndc_from_screen; let raster_from_screen = raster_from_ndc * ndc_from_screen;
let screen_from_raster = raster_from_screen.inverse(); let screen_from_raster = raster_from_screen.inverse();
let screen_from_camera = TransformGeneric::orthographic(0., 1.); let screen_from_camera = Transform::orthographic(0., 1.);
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster; let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.)); let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.));
let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.)); let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.));
@ -71,58 +77,16 @@ impl OrthographicCamera {
dy_camera, dy_camera,
} }
} }
pub fn create(
params: &ParameterDictionary,
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.);
let focal_distance = params.get_one_float("focaldistance", 1e6);
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
);
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
};
let sw = params.get_float_array("screenwindow");
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(format!("{}: screenwindow param must have four values", loc));
}
}
}
Ok(Self::new(base, screen, lens_radius, focal_distance))
}
} }
impl CameraTrait for OrthographicCamera { impl CameraTrait for OrthographicCamera {
fn base(&self) -> &CameraBase { #[cfg(not(target_os = "cuda"))]
&self.base fn init_metadata(&self, metadata: &mut ImageMetadata) {
self.base.init_metadata(metadata)
} }
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { fn base(&self) -> &CameraBase {
self.base.init_metadata(metadata) &self.base
} }
fn generate_ray( fn generate_ray(
@ -140,18 +104,14 @@ impl CameraTrait for OrthographicCamera {
Some(self.sample_time(sample.time)), Some(self.sample_time(sample.time)),
self.base().medium.clone(), self.base().medium.clone(),
); );
// Modify ray for depth of field
if self.lens_radius > 0. { if self.lens_radius > 0. {
// Sample point on lens
let p_lens_vec = let p_lens_vec =
self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens)); self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens));
let p_lens = Point2f::from(p_lens_vec); let p_lens = Point2f::from(p_lens_vec);
// Compute point on plane of focus
let ft = self.focal_distance / ray.d.z(); let ft = self.focal_distance / ray.d.z();
let p_focus = ray.at(ft); let p_focus = ray.at(ft);
// Update ray for effect of lens
ray.o = Point3f::new(p_lens.x(), p_lens.y(), 0.); ray.o = Point3f::new(p_lens.x(), p_lens.y(), 0.);
ray.d = (p_focus - ray.o).normalize(); ray.d = (p_focus - ray.o).normalize();
} }
@ -172,7 +132,18 @@ impl CameraTrait for OrthographicCamera {
let mut central_cam_ray = self.generate_ray(sample, lambda)?; let mut central_cam_ray = self.generate_ray(sample, lambda)?;
let mut rd = RayDifferential::default(); let mut rd = RayDifferential::default();
if self.lens_radius > 0.0 { if self.lens_radius > 0.0 {
return self.generate_ray_differential(sample, lambda); let mut sample_x = sample;
sample_x.p_film.x += 1.0;
let rx = self.generate_ray(sample_x, lambda)?;
let mut sample_y = sample;
sample_y.p_film.y += 1.0;
let ry = self.generate_ray(sample_y, lambda)?;
rd.rx_origin = rx.ray.o;
rd.ry_origin = ry.ray.o;
rd.rx_direction = rx.ray.d;
rd.ry_direction = ry.ray.d;
} else { } else {
let time = self.sample_time(sample.time); let time = self.sample_time(sample.time);
let world_dx = self let world_dx = self

View file

@ -1,20 +1,14 @@
use super::{CameraBase, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::camera::CameraBaseParameters; use crate::core::film::Film;
use crate::core::film::{Film, FilmTrait};
use crate::core::filter::FilterTrait;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike, Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
}; };
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::options::get_options;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::parameters::ParameterDictionary;
use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::sampling::sample_uniform_disk_concentric;
use crate::utils::transform::Transform; use crate::utils::transform::Transform;
use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
pub struct PerspectiveCamera { pub struct PerspectiveCamera {
@ -30,6 +24,7 @@ pub struct PerspectiveCamera {
pub cos_total_width: Float, pub cos_total_width: Float,
} }
#[cfg(not(target_os = "cuda"))]
impl PerspectiveCamera { impl PerspectiveCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,
@ -80,61 +75,18 @@ impl PerspectiveCamera {
cos_total_width, cos_total_width,
} }
} }
pub fn create(
params: &ParameterDictionary,
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.);
let focal_distance = params.get_one_float("focaldistance", 1e6);
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
);
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
};
let sw = params.get_float_array("screenwindow");
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(format!("{}: screenwindow param must have four values", loc));
}
}
}
let fov = params.get_one_float("fov", 90.);
Ok(Self::new(base, fov, screen, lens_radius, focal_distance))
}
} }
impl CameraTrait for PerspectiveCamera { impl PerspectiveCamera {
fn base(&self) -> &CameraBase { #[cfg(not(target_os = "cuda"))]
&self.base
}
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata) self.base.init_metadata(metadata)
} }
fn base(&self) -> &CameraBase {
&self.base
}
fn generate_ray( fn generate_ray(
&self, &self,
sample: CameraSample, sample: CameraSample,

View file

@ -1,9 +1,6 @@
use super::{
CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, ExitPupilSample,
LensElementInterface,
};
use crate::PI; use crate::PI;
use crate::core::film::{Film, FilmTrait}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::film::Film;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike, Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
}; };
@ -11,38 +8,59 @@ use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::core::scattering::refract; use crate::core::scattering::refract;
use crate::image::{Image, PixelFormat}; use crate::images::{Image, PixelFormat};
use crate::spectra::color::SRGB; use crate::spectra::color::SRGB;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::file::read_float_file;
use crate::utils::math::{lerp, quadratic, square}; use crate::utils::math::{lerp, quadratic, square};
use crate::utils::parameters::ParameterDictionary;
use std::path::Path;
use std::sync::Arc;
#[derive(Debug)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct LensElementInterface {
pub curvature_radius: Float,
pub thickness: Float,
pub eta: Float,
pub aperture_radius: Float,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct ExitPupilSample {
pub p_pupil: Point3f,
pub pdf: Float,
}
const EXIT_PUPIL_SAMPLES: usize = 64;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct RealisticCamera { pub struct RealisticCamera {
base: CameraBase, base: CameraBase,
focus_distance: Float, focus_distance: Float,
set_aperture_diameter: Float, set_aperture_diameter: Float,
aperture_image: Option<Image>, aperture_image: *const Image,
element_interface: Vec<LensElementInterface>, element_interfaces: *const LensElementInterface,
n_elements: usize,
physical_extent: Bounds2f, physical_extent: Bounds2f,
exit_pupil_bounds: Vec<Bounds2f>, exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
} }
#[cfg(not(target_os = "cuda"))]
impl RealisticCamera { impl RealisticCamera {
pub fn new( pub fn new(
base: CameraBase, base: CameraBase,
lens_params: Vec<Float>, lens_params: &[Float],
focus_distance: Float, focus_distance: Float,
set_aperture_diameter: Float, set_aperture_diameter: Float,
aperture_image: Option<Image>, aperture_image: Option<Image>,
) -> Self { ) -> Self {
let aspect = let film_ptr = base.film;
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float; if film_ptr.is_null() {
let diagonal = base.film.diagonal(); panic!("Camera must have a film");
}
let film = unsafe { &*film_ptr };
let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float;
let diagonal = film.diagonal();
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt(); let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
let y = x * aspect; let y = x * aspect;
let physical_extent = let physical_extent =
@ -73,19 +91,24 @@ impl RealisticCamera {
} }
let n_samples = 64; let n_samples = 64;
let half_diag = base.film.diagonal() / 2.0; let half_diag = film.diagonal() / 2.0;
let exit_pupil_bounds: Vec<_> = (0..n_samples) let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
.map(|i| {
let r0 = (i as Float / n_samples as Float) * half_diag; for i in 0..EXIT_PUPIL_SAMPLES {
let r1 = ((i + 1) as Float / n_samples as Float) * half_diag; let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
Self::compute_exit_pupil_bounds(&element_interface, r0, r1) let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
}) exit_pupil_bounds[i] = Self::compute_exit_pupil_bounds(&element_interface, r0, r1);
.collect(); }
let n_elements = element_interface.len();
let element_interfaces = element_interface.as_ptr();
std::mem::forget(element_interface);
Self { Self {
base, base,
focus_distance, focus_distance,
element_interface, element_interfaces,
n_elements,
physical_extent, physical_extent,
set_aperture_diameter, set_aperture_diameter,
aperture_image, aperture_image,
@ -93,20 +116,58 @@ impl RealisticCamera {
} }
} }
pub fn lens_rear_z(&self) -> Float { pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) {
self.element_interface.last().unwrap().thickness let tf = -r_out.o.x() / r_out.d.x();
let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x();
(-r_out.at(tf).z(), -r_out.at(tp).z())
} }
pub fn lens_front_z(&self) -> Float { pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) {
let mut z_sum = 0.; let x = 0.001 * self.get_film().diagonal();
for element in &self.element_interface { let r_scene = Ray::new(
z_sum += element.thickness; Point3f::new(0., x, self.lens_front_z() + 1.),
Vector3f::new(0., 0., -1.),
None,
None,
);
let Some(r_film) = self.trace_lenses_from_film(r_scene) else {
panic!(
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
)
};
let (pz0, fz0) = Self::compute_cardinal_points(r_scene, r_film);
let r_film = Ray::new(
Point3f::new(x, 0., self.lens_rear_z() - 1.),
Vector3f::new(0., 0., 1.),
None,
None,
);
let Some(r_scene) = self.trace_lenses_from_film(r_film) else {
panic!(
"Unable to trace ray from scene to film for thick lens approx. Is aperture very small?"
)
};
let (pz1, f_1) = Self::compute_cardinal_points(r_film, r_scene);
([pz0, pz1], [fz0, fz1])
}
pub fn focus_thick_lens(&self, focus_distance: Float) -> Float {
let (pz, fz) = self.compute_thick_lens_approximation();
let f = fz[0] - pz[0];
let z = -focus_distance;
let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0]);
if c <= 0 {
panic!(
"Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration",
focusDistance
);
} }
z_sum let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.;
self.element_interface.last().thickness + delta
} }
pub fn rear_element_radius(&self) -> Float { pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
self.element_interface.last().unwrap().aperture_radius Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1)
} }
fn compute_exit_pupil_bounds( fn compute_exit_pupil_bounds(
@ -160,38 +221,40 @@ impl RealisticCamera {
pupil_bounds pupil_bounds
} }
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f { pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1) // Find exit pupil bound for sample distance from film center
} let film = self.film();
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
let mut r_index = (r_film / (film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
pub fn intersect_spherical_element( let pupil_bounds = self.exit_pupil_bounds[r_index];
radius: Float, if pupil_bounds.is_degenerate() {
z_center: Float,
ray: &Ray,
) -> Option<(Float, Normal3f)> {
let o = ray.o - Vector3f::new(0.0, 0.0, z_center);
let a = ray.d.norm_squared();
let b = 2.0 * ray.d.dot(o.into());
let c = Vector3f::from(o).norm_squared() - radius * radius;
let (t0, t1) = quadratic(a, b, c)?;
let use_closer_t = (ray.d.z() > 0.0) ^ (radius < 0.0);
let t = if use_closer_t { t0.min(t1) } else { t0.max(t1) };
// Intersection is behind the ray's origin.
if t < 0.0 {
return None; return None;
} }
let p_hit_relative = o + ray.d * t; // Generate sample point inside exit pupil bound
// Ensures the normal points towards the incident ray. let p_lens = pupil_bounds.lerp(u_lens);
let n = Normal3f::from(Vector3f::from(p_hit_relative)) let pdf = 1. / pupil_bounds.area();
.normalize()
.face_forward(-ray.d);
Some((t, n)) // Return sample point rotated by angle of _pFilm_ with $+x$ axis
let sin_theta = if r_film != 0. {
p_film.y() / r_film
} else {
0.
};
let cos_theta = if r_film != 0. {
p_film.x() / r_film
} else {
1.
};
let p_pupil = Point3f::new(
cos_theta * p_lens.x() - sin_theta * p_lens.y(),
sin_theta * p_lens.x() + cos_theta * p_lens.y(),
self.lens_rear_z(),
);
Some(ExitPupilSample { p_pupil, pdf })
} }
pub fn trace_lenses_from_film(&self, r_camera: &Ray) -> Option<(Float, Ray)> { pub fn trace_lenses_from_film(&self, r_camera: &Ray) -> Option<(Float, Ray)> {
@ -274,229 +337,72 @@ impl RealisticCamera {
Some((weight, r_out)) Some((weight, r_out))
} }
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> { fn intersect_spherical_element(
// Find exit pupil bound for sample distance from film center radius: Float,
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt(); z_center: Float,
let mut r_index = ray: &Ray,
(r_film / (self.base.film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len(); ) -> Option<(Float, Normal3f)> {
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index); let o = ray.o - Vector3f::new(0.0, 0.0, z_center);
let pupil_bounds = self.exit_pupil_bounds[r_index]; let a = ray.d.norm_squared();
if pupil_bounds.is_degenerate() { let b = 2.0 * ray.d.dot(o.into());
let c = Vector3f::from(o).norm_squared() - radius * radius;
let (t0, t1) = quadratic(a, b, c)?;
let use_closer_t = (ray.d.z() > 0.0) ^ (radius < 0.0);
let t = if use_closer_t { t0.min(t1) } else { t0.max(t1) };
// Intersection is behind the ray's origin.
if t < 0.0 {
return None; return None;
} }
// Generate sample point inside exit pupil bound let p_hit_relative = o + ray.d * t;
let p_lens = pupil_bounds.lerp(u_lens); // Ensures the normal points towards the incident ray.
let pdf = 1. / pupil_bounds.area(); let n = Normal3f::from(Vector3f::from(p_hit_relative))
.normalize()
.face_forward(-ray.d);
// Return sample point rotated by angle of _pFilm_ with $+x$ axis Some((t, n))
let sin_theta = if r_film != 0. {
p_film.y() / r_film
} else {
0.
};
let cos_theta = if r_film != 0. {
p_film.x() / r_film
} else {
1.
};
let p_pupil = Point3f::new(
cos_theta * p_lens.x() - sin_theta * p_lens.y(),
sin_theta * p_lens.x() + cos_theta * p_lens.y(),
self.lens_rear_z(),
);
Some(ExitPupilSample { p_pupil, pdf })
} }
pub fn create( pub fn lens_rear_z(&self) -> Float {
params: &ParameterDictionary, self.element_interface.last().unwrap().thickness
camera_transform: &CameraTransform, }
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let aperture_diameter = params.get_one_float("aperturediameter", 1.);
let focal_distance = params.get_one_float("focaldistance", 10.);
let lens_file = params.get_one_string("lensfile", "");
if lens_file.is_empty() { pub fn lens_front_z(&self) -> Float {
return Err(format!("{}: No lens file supplied", loc)); let mut z_sum = 0.;
for element in &self.element_interface {
z_sum += element.thickness;
} }
z_sum
}
let lens_params = read_float_file(lens_file.as_str()).map_err(|e| e.to_string())?; pub fn rear_element_radius(&self) -> Float {
if lens_params.len() % 4 != 0 { self.element_interface.last().unwrap().aperture_radius
return Err(format!(
"{}: excess values in lens specification file; must be multiple-of-four values, read {}",
loc,
lens_params.len()
));
}
let builtin_res = 256;
let rasterize = |vert: &[Point2f]| -> Image {
let mut image = Image::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB,
);
let res = image.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
// Map pixel to [-1, 1] range
let p = Point2f::new(
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
);
let mut winding_number = 0;
// Winding number test against edges
for i in 0..vert.len() {
let i1 = (i + 1) % vert.len();
let v_i = vert[i];
let v_i1 = vert[i1];
let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y())
- (p.y() - v_i.y()) * (v_i1.x() - v_i.x());
if v_i.y() <= p.y() {
if v_i1.y() > p.y() && e > 0.0 {
winding_number += 1;
}
} else if v_i1.y() <= p.y() && e < 0.0 {
winding_number -= 1;
}
}
image.set_channel(
Point2i::new(x, y),
0,
if winding_number == 0 { 0.0 } else { 1.0 },
);
}
}
image
};
let aperture_name = params.get_one_string("aperture", "");
let mut aperture_image: Option<Image> = None;
if !aperture_name.is_empty() {
match aperture_name.as_str() {
"gaussian" => {
let mut img = Image::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB,
);
let res = img.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let uv = Point2f::new(
-1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float,
-1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float,
);
let r2 = square(uv.x()) + square(uv.y());
let sigma2 = 1.0;
let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0);
img.set_channel(Point2i::new(x, y), 0, v);
}
}
aperture_image = Some(img);
}
"square" => {
let mut img = Image::new(
PixelFormat::F32,
Point2i::new(builtin_res, builtin_res),
&["Y"],
SRGB,
);
let low = (0.25 * builtin_res as Float) as i32;
let high = (0.75 * builtin_res as Float) as i32;
for y in low..high {
for x in low..high {
img.set_channel(Point2i::new(x, y), 0, 4.0);
}
}
aperture_image = Some(img);
}
"pentagon" => {
let c1 = (5.0f32.sqrt() - 1.0) / 4.0;
let c2 = (5.0f32.sqrt() + 1.0) / 4.0;
let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0;
let mut vert = [
Point2f::new(0.0, 1.0),
Point2f::new(s1, c1),
Point2f::new(s2, -c2),
Point2f::new(-s2, -c2),
Point2f::new(-s1, c1),
];
for v in vert.iter_mut() {
*v = Point2f::from(Vector2f::from(*v) * 0.8);
}
aperture_image = Some(rasterize(&vert));
}
"star" => {
let mut vert = Vec::with_capacity(10);
for i in 0..10 {
let r = if i % 2 == 1 {
1.0
} else {
(72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos())
};
let angle = PI * i as Float / 5.0;
vert.push(Point2f::new(r * angle.cos(), r * angle.sin()));
}
vert.reverse();
aperture_image = Some(rasterize(&vert));
}
_ => {
if let Ok(im) = Image::read(Path::new(&aperture_name), None) {
if im.image.n_channels() > 1 {
let mut mono =
Image::new(PixelFormat::F32, im.image.resolution(), &["Y"], SRGB);
let res = mono.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let avg =
im.image.get_channels_default(Point2i::new(x, y)).average();
mono.set_channel(Point2i::new(x, y), 0, avg);
}
}
aperture_image = Some(mono);
} else {
aperture_image = Some(im.image);
}
}
}
}
}
Ok(Self::new(
base,
lens_params,
focal_distance,
aperture_diameter,
aperture_image,
))
} }
} }
impl CameraTrait for RealisticCamera { impl CameraTrait for RealisticCamera {
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
self.base.init_metadata(metadata)
}
fn base(&self) -> &CameraBase { fn base(&self) -> &CameraBase {
&self.base &self.base
} }
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { fn get_film(&self) -> &Film {
self.base.init_metadata(metadata) #[cfg(not(target_os = "cuda"))]
{
if self.base.film.is_null() {
panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
}
}
unsafe { &*self.base.film }
} }
fn generate_ray( fn generate_ray(
@ -505,9 +411,10 @@ impl CameraTrait for RealisticCamera {
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
) -> Option<CameraRay> { ) -> Option<CameraRay> {
// Find point on film, _pFilm_, corresponding to _sample.pFilm_ // Find point on film, _pFilm_, corresponding to _sample.pFilm_
let film = self.get_film();
let s = Point2f::new( let s = Point2f::new(
sample.p_film.x() / self.base.film.full_resolution().x() as Float, sample.p_film.x() / film.full_resolution().x() as Float,
sample.p_film.y() / self.base.film.full_resolution().y() as Float, sample.p_film.y() / film.full_resolution().y() as Float,
); );
let p_film2 = self.physical_extent.lerp(s); let p_film2 = self.physical_extent.lerp(s);
let p_film = Point3f::new(-p_film2.x(), p_film2.y(), 0.); let p_film = Point3f::new(-p_film2.x(), p_film2.y(), 0.);

View file

@ -1,91 +1,30 @@
use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTransform};
use crate::core::film::{Film, FilmTrait}; use crate::core::film::Film;
use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction}; use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::options::get_options;
use crate::core::pbrt::{Float, PI}; use crate::core::pbrt::{Float, PI};
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square}; use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
use crate::utils::parameters::ParameterDictionary;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, PartialEq)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Mapping { pub enum Mapping {
EquiRectangular, EquiRectangular,
EqualArea, EqualArea,
} }
#[derive(Debug)] #[derive(Debug, Copy, Clone)]
pub struct SphericalCamera { pub struct SphericalCamera {
pub base: CameraBase,
pub screen: Bounds2f,
pub lens_radius: Float,
pub focal_distance: Float,
pub mapping: Mapping, pub mapping: Mapping,
pub base: CameraBase,
} }
#[cfg(not(target_os = "cuda"))]
impl SphericalCamera { impl SphericalCamera {
pub fn create( pub fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) {
params: &ParameterDictionary, self.base.init_metadata(metadata)
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Medium,
loc: &FileLoc,
) -> Result<Self, String> {
let full_res = film.full_resolution();
let camera_params =
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc);
let base = CameraBase::new(camera_params);
let lens_radius = params.get_one_float("lensradius", 0.);
let focal_distance = params.get_one_float("focaldistance", 1e30);
let frame = params.get_one_float(
"frameaspectratio",
full_res.x() as Float / full_res.y() as Float,
);
let mut screen = if frame > 1. {
Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.))
} else {
Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame))
};
let sw = params.get_float_array("screenwindow");
if !sw.is_empty() {
if get_options().fullscreen {
eprint!("Screenwindow is ignored in fullscreen mode");
} else {
if sw.len() == 4 {
screen = Bounds2f::from_points(
Point2f::new(sw[0], sw[2]),
Point2f::new(sw[1], sw[3]),
);
} else {
return Err(format!("{}: screenwindow param must have four values", loc));
}
}
}
let m = params.get_one_string("mapping", "equalarea");
let mapping = match m.as_str() {
"equal_area" => Mapping::EqualArea,
"equirectangular" => Mapping::EquiRectangular,
_ => {
return Err(format!(
"{}: unknown mapping for spherical camera at {}",
m, loc
));
}
};
Ok(Self {
mapping,
base,
screen,
lens_radius,
focal_distance,
})
} }
} }
@ -94,8 +33,16 @@ impl CameraTrait for SphericalCamera {
&self.base &self.base
} }
fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { fn get_film(&self) -> &Film {
self.base.init_metadata(metadata) #[cfg(not(target_os = "cuda"))]
{
if self.base.film.is_null() {
panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
}
}
unsafe { &*self.base.film }
} }
fn generate_ray( fn generate_ray(
@ -104,9 +51,10 @@ impl CameraTrait for SphericalCamera {
_lamdba: &SampledWavelengths, _lamdba: &SampledWavelengths,
) -> Option<CameraRay> { ) -> Option<CameraRay> {
// Compute spherical camera ray direction // Compute spherical camera ray direction
let film = self.get_film();
let mut uv = Point2f::new( let mut uv = Point2f::new(
sample.p_film.x() / self.base().film.full_resolution().x() as Float, sample.p_film.x() / film.full_resolution().x() as Float,
sample.p_film.y() / self.base().film.full_resolution().y() as Float, sample.p_film.y() / film.full_resolution().y() as Float,
); );
let dir: Vector3f; let dir: Vector3f;
if self.mapping == Mapping::EquiRectangular { if self.mapping == Mapping::EquiRectangular {

View file

@ -10,6 +10,7 @@ use std::cmp::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SplitMethod { pub enum SplitMethod {
SAH, SAH,
@ -18,10 +19,11 @@ pub enum SplitMethod {
EqualCounts, EqualCounts,
} }
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
struct BVHSplitBucket { struct BVHSplitBucket {
count: usize, pub count: usize,
bounds: Bounds3f, pub bounds: Bounds3f,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]

View file

@ -1,4 +1,5 @@
use crate::core::film::{Film, FilmTrait}; use crate::cameras::*;
use crate::core::film::Film;
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike, Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike,
}; };
@ -9,34 +10,31 @@ use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::images::ImageMetadata; use crate::images::ImageMetadata;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::error::FileLoc;
use crate::utils::math::lerp; use crate::utils::math::lerp;
use crate::utils::parameters::ParameterDictionary;
use crate::utils::transform::{AnimatedTransform, Transform}; use crate::utils::transform::{AnimatedTransform, Transform};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct CameraRay { pub struct CameraRay {
pub ray: Ray, pub ray: Ray,
pub weight: SampledSpectrum, pub weight: SampledSpectrum,
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct CameraWiSample { pub struct CameraWiSample {
wi_spec: SampledSpectrum, pub wi_spec: SampledSpectrum,
wi: Vector3f, pub wi: Vector3f,
pdf: Float, pub pdf: Float,
p_raster: Point2f, pub p_raster: Point2f,
p_ref: Interaction, pub p_ref: Interaction,
p_lens: Interaction, pub p_lens: Interaction,
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CameraTransform { pub struct CameraTransform {
pub render_from_camera: AnimatedTransform, pub render_from_camera: AnimatedTransform,
pub world_from_render: Transform, pub world_from_render: Transform,
@ -106,55 +104,20 @@ impl CameraTransform {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Debug, Copy, Clone)]
pub struct CameraBaseParameters {
pub camera_transform: CameraTransform,
pub shutter_open: Float,
pub shutter_close: Float,
pub film: Arc<Film>,
pub medium_id: i32,
}
impl CameraBaseParameters {
pub fn new(
camera_transform: &CameraTransform,
film: Arc<Film>,
medium: Arc<Medium>,
params: &ParameterDictionary,
loc: &FileLoc,
) -> Self {
let mut shutter_open = params.get_one_float("shutteropen", 0.);
let mut shutter_close = params.get_one_float("shutterclose", 1.);
if shutter_close < shutter_open {
eprint!(
"{}: Shutter close time {} < shutter open {}. Swapping",
loc, shutter_close, shutter_open
);
std::mem::swap(&mut shutter_open, &mut shutter_close);
}
CameraBaseParameters {
camera_transform: Arc::new(camera_transform.clone()),
shutter_open,
shutter_close,
film,
medium_id: 0,
}
}
}
#[derive(Debug)]
pub struct CameraBase { pub struct CameraBase {
pub camera_transform: CameraTransform, pub camera_transform: CameraTransform,
pub shutter_open: Float, pub shutter_open: Float,
pub shutter_close: Float, pub shutter_close: Float,
pub film: Arc<Film>, pub film: *const Film,
pub medium: Option<Arc<Medium>>, pub medium: *const Medium,
pub min_pos_differential_x: Vector3f, pub min_pos_differential_x: Vector3f,
pub min_pos_differential_y: Vector3f, pub min_pos_differential_y: Vector3f,
pub min_dir_differential_x: Vector3f, pub min_dir_differential_x: Vector3f,
pub min_dir_differential_y: Vector3f, pub min_dir_differential_y: Vector3f,
} }
#[cfg(not(target_os = "cuda"))]
impl CameraBase { impl CameraBase {
pub fn init_metadata(&self, metadata: &mut ImageMetadata) { pub fn init_metadata(&self, metadata: &mut ImageMetadata) {
let camera_from_world: Transform = let camera_from_world: Transform =
@ -162,83 +125,97 @@ impl CameraBase {
metadata.camera_from_world = Some(camera_from_world.get_matrix()); metadata.camera_from_world = Some(camera_from_world.get_matrix());
} }
}
pub fn new(p: CameraBaseParameters) -> Self { #[enum_dispatch(CameraTrait)]
Self { #[repr(C)]
camera_transform: p.camera_transform.as_ref().clone(), #[derive(Debug, Copy, Clone)]
shutter_open: p.shutter_open, pub enum Camera {
shutter_close: p.shutter_close, Perspective(PerspectiveCamera),
film: p.film.clone(), Orthographic(OrthographicCamera),
medium: p.medium, Spherical(SphericalCamera),
min_pos_differential_x: Vector3f::default(), Realistic(RealisticCamera),
min_pos_differential_y: Vector3f::default(),
min_dir_differential_x: Vector3f::default(),
min_dir_differential_y: Vector3f::default(),
}
}
} }
#[enum_dispatch] #[enum_dispatch]
pub trait CameraTrait { pub trait CameraTrait {
#[cfg(not(target_os = "cuda"))]
fn init_metadata(&self, metadata: &mut ImageMetadata);
fn base(&self) -> &CameraBase; fn base(&self) -> &CameraBase;
fn get_film(&self) -> &Film {
&self.base().film fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
#[cfg(not(target_os = "cuda"))]
fn get_film(&self) -> Result<&Film, String> {
if self.film.is_null() {
return Err("Camera error: Film pointer is null.".to_string());
}
Ok(unsafe { &*self.film })
}
fn sample_time(&self, u: Float) -> Float {
lerp(u, self.base().shutter_open, self.base().shutter_close)
} }
fn resolution(&self) -> Point2i { fn resolution(&self) -> Point2i {
self.base().film.full_resolution() self.base().film.full_resolution()
} }
fn init_metadata(&self, metadata: &mut ImageMetadata); fn render_from_camera(&self, r: &Ray, t_max: &mut Option<Float>) -> Ray {
self.base()
.camera_transform
.render_from_camera_ray(r, t_max)
}
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
fn generate_ray_differential( fn generate_ray_differential(
&self, &self,
sample: CameraSample, sample: CameraSample,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> Option<CameraRay> { ) -> Option<CameraRay> {
let mut central_cam_ray = self.generate_ray(sample, lambda)?; match self {
let mut rd = RayDifferential::default(); Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
let mut rx_found = false; _ => {
let mut ry_found = false; let mut central_cam_ray = self.generate_ray(sample, lambda)?;
let mut rd = RayDifferential::default();
let mut rx_found = false;
let mut ry_found = false;
for eps in [0.05, -0.05] { for eps in [0.05, -0.05] {
let mut s_shift = sample; let mut s_shift = sample;
s_shift.p_film[0] += eps; s_shift.p_film[0] += eps;
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) { if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
rd.rx_origin = rd.rx_origin = central_cam_ray.ray.o
central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps; + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
rd.rx_direction = rd.rx_direction = central_cam_ray.ray.d
central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps; + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
rx_found = true; rx_found = true;
break; break;
}
}
for eps in [0.05, -0.05] {
let mut s_shift = sample;
s_shift.p_film[1] += eps;
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
rd.ry_origin = central_cam_ray.ray.o
+ (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
rd.ry_direction = central_cam_ray.ray.d
+ (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
ry_found = true;
break;
}
}
if rx_found && ry_found {
central_cam_ray.ray.differential = Some(rd);
}
Some(central_cam_ray)
} }
} }
for eps in [0.05, -0.05] {
let mut s_shift = sample;
s_shift.p_film[1] += eps;
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
rd.ry_origin =
central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
rd.ry_direction =
central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
ry_found = true;
break;
}
}
if rx_found && ry_found {
central_cam_ray.ray.differential = Some(rd);
}
Some(central_cam_ray)
}
fn sample_time(&self, u: Float) -> Float {
lerp(u, self.base().shutter_open, self.base().shutter_close)
} }
fn approximate_dp_dxy( fn approximate_dp_dxy(
@ -290,65 +267,4 @@ pub trait CameraTrait {
time, time,
); );
} }
fn render_from_camera(&self, r: &Ray, t_max: &mut Option<Float>) -> Ray {
self.base()
.camera_transform
.render_from_camera_ray(r, t_max)
}
}
#[enum_dispatch(CameraTrait)]
#[derive(Debug)]
pub enum Camera {
Perspective(PerspectiveCamera),
Orthographic(OrthographicCamera),
Spherical(SphericalCamera),
Realistic(RealisticCamera),
}
impl Camera {
pub fn create(
name: &str,
params: &ParameterDictionary,
medium: Medium,
camera_transform: &CameraTransform,
film: Arc<Film>,
loc: &FileLoc,
) -> Result<Self, String> {
match name {
"perspective" => {
let camera =
PerspectiveCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Perspective(camera))
}
"orthographic" => {
let camera =
OrthographicCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Orthographic(camera))
}
"realistic" => {
let camera = RealisticCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Realistic(camera))
}
"spherical" => {
let camera = SphericalCamera::create(params, camera_transform, film, medium, loc)?;
Ok(Camera::Spherical(camera))
}
_ => Err(format!("Camera type '{}' unknown at {}", name, loc)),
}
}
}
#[derive(Debug)]
pub struct LensElementInterface {
pub curvature_radius: Float,
pub thickness: Float,
pub eta: Float,
pub aperture_radius: Float,
}
pub struct ExitPupilSample {
pub p_pupil: Point3f,
pub pdf: Float,
} }

View file

@ -5,17 +5,12 @@ use std::ops::{
}; };
use crate::core::geometry::Point2f; use crate::core::geometry::Point2f;
use crate::core::pbrt::Float; use crate::core::pbrt::{Float, find_interval};
use crate::spectra::Spectrum; use crate::core::spectrum::Spectrum;
use crate::utils::math::{SquareMatrix, evaluate_polynomial, lerp}; use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
use once_cell::sync::Lazy;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub trait Triplet {
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self;
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct XYZ { pub struct XYZ {
pub x: Float, pub x: Float,
@ -23,9 +18,9 @@ pub struct XYZ {
pub z: Float, pub z: Float,
} }
impl Triplet for XYZ { impl From<(Float, Float, Float)> for XYZ {
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self { fn from(triplet: (Float, Float, Float)) -> Self {
XYZ::new(c1, c2, c3) XYZ::new(triplet.0, triplet.1, triplet.2)
} }
} }
@ -259,9 +254,9 @@ pub struct RGB {
pub b: Float, pub b: Float,
} }
impl Triplet for RGB { impl From<(Float, Float, Float)> for RGB {
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self { fn from(triplet: (Float, Float, Float)) -> Self {
RGB::new(c1, c2, c3) RGB::new(triplet.0, triplet.1, triplet.2)
} }
} }
@ -467,7 +462,7 @@ impl fmt::Display for RGB {
} }
} }
impl Mul<XYZ> for SquareMatrix<Float, 3> { impl Mul<XYZ> for SquareMatrix3f {
type Output = RGB; type Output = RGB;
fn mul(self, v: XYZ) -> RGB { fn mul(self, v: XYZ) -> RGB {
@ -478,7 +473,7 @@ impl Mul<XYZ> for SquareMatrix<Float, 3> {
} }
} }
impl Mul<RGB> for SquareMatrix<Float, 3> { impl Mul<RGB> for SquareMatrix3f {
type Output = XYZ; type Output = XYZ;
fn mul(self, v: RGB) -> XYZ { fn mul(self, v: RGB) -> XYZ {
let x = self[0][0] * v.r + self[0][1] * v.g + self[0][2] * v.b; let x = self[0][0] * v.r + self[0][1] * v.g + self[0][2] * v.b;
@ -493,7 +488,7 @@ pub trait MatrixMulColor {
fn mul_xyz(&self, v: XYZ) -> XYZ; fn mul_xyz(&self, v: XYZ) -> XYZ;
} }
impl MatrixMulColor for SquareMatrix<Float, 3> { impl MatrixMulColor for SquareMatrix3f {
fn mul_rgb(&self, v: RGB) -> RGB { fn mul_rgb(&self, v: RGB) -> RGB {
let m = self; let m = self;
RGB::new( RGB::new(
@ -513,65 +508,22 @@ impl MatrixMulColor for SquareMatrix<Float, 3> {
} }
} }
pub const RES: usize = 64; #[repr(C)]
pub type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3];
#[derive(Debug)]
pub struct RGBToSpectrumTable {
coeffs: &'static [Float],
scale: &'static [Float],
}
impl RGBToSpectrumTable {
pub fn srgb() -> Self {
Self {
coeffs: *crate::data::SRGB_COEFFS,
scale: *crate::data::SRGB_SCALE,
}
}
pub fn dci_p3() -> Self {
Self {
coeffs: *crate::data::DCI_P3_COEFFS,
scale: *crate::data::DCI_P3_SCALE,
}
}
pub fn rec2020() -> Self {
Self {
coeffs: *crate::data::REC2020_COEFFS,
scale: *crate::data::REC2020_SCALE,
}
}
pub fn aces2065_1() -> Self {
Self {
coeffs: *crate::data::ACES_COEFFS,
scale: *crate::data::ACES_SCALE,
}
}
}
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub struct RGBSigmoidPolynomial { pub struct RGBSigmoidPolynomial {
c0: Float, pub c0: Float,
c1: Float, pub c1: Float,
c2: Float, pub c2: Float,
} }
impl RGBSigmoidPolynomial { impl RGBSigmoidPolynomial {
#[cfg(not(target_os = "cuda"))]
pub fn new(c0: Float, c1: Float, c2: Float) -> Self { pub fn new(c0: Float, c1: Float, c2: Float) -> Self {
Self { c0, c1, c2 } Self { c0, c1, c2 }
} }
pub fn evaluate(&self, lambda: Float) -> Float { pub fn evaluate(&self, lambda: Float) -> Float {
let eval = match evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) { let eval = evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]);
Some(value) => value,
None => {
panic!("evaluate_polynomial returned None with non-empty coefficients")
}
};
Self::s(eval) Self::s(eval)
} }
@ -586,120 +538,16 @@ impl RGBSigmoidPolynomial {
fn s(x: Float) -> Float { fn s(x: Float) -> Float {
if x.is_infinite() { if x.is_infinite() {
if x > 0.0 { return 1.0 } else { return 0.0 } if x > 0.0 {
return 1.0;
} else {
return 0.0;
}
} }
0.5 + x / (2.0 * (1.0 + (x * x)).sqrt()) 0.5 + x / (2.0 * (1.0 + (x * x)).sqrt())
} }
} }
impl RGBToSpectrumTable {
pub fn new(scale: &'static [Float], coeffs: &'static [Float]) -> Self {
Self { scale, coeffs }
}
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
//TODO: Check all of this logic, this is important
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
return RGBSigmoidPolynomial::new(
0.0,
0.0,
(rgb[0] - 0.5) / (rgb[0] * (1.0 - rgb[0])).sqrt(),
);
}
let max_c = rgb.max_component_value();
let c = [rgb.r, rgb.g, rgb.b];
let (min_c, mid_c) = if c[0] < c[1] {
if c[1] < c[2] {
(c[0], c[1])
}
// 0 < 1 < 2
else if c[0] < c[2] {
(c[0], c[2])
}
// 0 < 2 < 1
else {
(c[2], c[0])
} // 2 < 0 < 1
} else {
if c[1] > c[2] {
(c[2], c[1])
}
// 2 < 1 < 0
else if c[0] > c[2] {
(c[1], c[2])
}
// 1 < 2 < 0
else {
(c[1], c[0])
} // 1 < 0 < 2
};
let z = min_c / max_c;
let x = mid_c / max_c;
let z_float = z * (RES - 1) as Float;
let zi = (z_float as usize).min(RES - 2);
let z_t = z_float - zi as Float;
let x_float = x * (RES - 1) as Float;
let xi = (x_float as usize).min(RES - 2);
let x_t = x_float - xi as Float;
let mut coeffs = [0.0; 3];
#[allow(clippy::needless_range_loop)]
for i in 0..3 {
let offset = |dz, dx| (((zi + dz) * RES + (xi + dx)) * RES + (RES - 1)) * 3 + i;
let c00 = self.coeffs[offset(0, 0)];
let c01 = self.coeffs[offset(0, 1)];
let c10 = self.coeffs[offset(1, 0)];
let c11 = self.coeffs[offset(1, 1)];
let v0 = lerp(x_t, c00, c01);
let v1 = lerp(x_t, c10, c11);
coeffs[i] = lerp(z_t, v0, v1);
}
let scale_float = max_c * (RES - 1) as Float;
let si = (scale_float as usize).min(RES - 2);
let s_t = scale_float - si as Float;
let scale = lerp(s_t, self.scale[si], self.scale[si + 1]);
RGBSigmoidPolynomial {
c0: coeffs[0],
c1: coeffs[1],
c2: coeffs[2],
}
}
}
const LMS_FROM_XYZ: SquareMatrix<Float, 3> = SquareMatrix::new([
[0.8951, 0.2664, -0.1614],
[-0.7502, 1.7135, 0.0367],
[0.0389, -0.0685, 1.0296],
]);
const XYZ_FROM_LMS: SquareMatrix<Float, 3> = SquareMatrix::new([
[0.986993, -0.147054, 0.159963],
[0.432305, 0.51836, 0.0492912],
[-0.00852866, 0.0400428, 0.968487],
]);
pub fn white_balance(src_white: Point2f, target_white: Point2f) -> SquareMatrix<Float, 3> {
// Find LMS coefficients for source and target white
let src_xyz = XYZ::from_xyy(src_white, None);
let dst_xyz = XYZ::from_xyy(target_white, None);
let src_lms = LMS_FROM_XYZ * src_xyz;
let dst_lms = LMS_FROM_XYZ * dst_xyz;
// Return white balancing matrix for source and target white
let lms_correct = SquareMatrix::<Float, 3>::diag(&[
dst_lms[0] / src_lms[0],
dst_lms[1] / src_lms[1],
dst_lms[2] / src_lms[2],
]);
XYZ_FROM_LMS * lms_correct * LMS_FROM_XYZ
}
#[enum_dispatch] #[enum_dispatch]
pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display { pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display {
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]); fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]);
@ -710,6 +558,7 @@ pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display
} }
} }
#[repr(C)]
#[enum_dispatch(ColorEncodingTrait)] #[enum_dispatch(ColorEncodingTrait)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum ColorEncoding { pub enum ColorEncoding {
@ -723,6 +572,7 @@ impl fmt::Display for ColorEncoding {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct LinearEncoding; pub struct LinearEncoding;
impl ColorEncodingTrait for LinearEncoding { impl ColorEncodingTrait for LinearEncoding {
@ -747,6 +597,7 @@ impl fmt::Display for LinearEncoding {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct SRGBEncoding; pub struct SRGBEncoding;
impl ColorEncodingTrait for SRGBEncoding { impl ColorEncodingTrait for SRGBEncoding {
@ -1045,3 +896,122 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [
0.9911022186, 0.9911022186,
1.0000000000, 1.0000000000,
]; ];
pub const RES: usize = 64;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Coeffs {
pub c0: Float,
pub c1: Float,
pub c2: Float,
}
impl Add for Coeffs {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self {
Self {
c0: self.c0 + rhs.c0,
c1: self.c1 + rhs.c1,
c2: self.c2 + rhs.c2,
}
}
}
impl Mul<Float> for Coeffs {
type Output = Self;
#[inline(always)]
fn mul(self, s: Float) -> Self {
Self {
c0: self.c0 * s,
c1: self.c1 * s,
c2: self.c2 * s,
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct RGBToSpectrumTable {
pub z_nodes: *const Float,
pub coeffs: *const Coeffs,
}
unsafe impl Send for RGBToSpectrumTable {}
unsafe impl Sync for RGBToSpectrumTable {}
impl RGBToSpectrumTable {
#[inline(always)]
fn get_coeffs(&self, bucket: usize, z: usize, y: usize, x: usize) -> Coeffs {
let offset = bucket * (RES * RES * RES) + z * (RES * RES) + y * (RES) + x;
unsafe { *self.coeffs.add(offset) }
}
pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial {
let m = rgb.max_component_value();
let min_val = rgb.min_component_value();
if m - min_val < 1e-4 {
let x = clamp(rgb[0], 1e-4, 0.9999);
let c2 = (0.5 - x) / (x * (1.0 - x)).sqrt();
return RGBSigmoidPolynomial::new(0.0, 0.0, c2);
}
// Identify the primary bucket (c) based on the dominant axis
let c_idx = if rgb[0] > rgb[1] {
if rgb[0] > rgb[2] { 0 } else { 2 }
} else if rgb[1] > rgb[2] {
1
} else {
2
};
let z = m;
let (x_val, y_val) = match c_idx {
0 => (rgb[1], rgb[2]), // R is max -> G, B
1 => (rgb[0], rgb[2]), // G is max -> R, B
_ => (rgb[0], rgb[1]), // B is max -> R, G
};
let (coord_a, coord_b) = if x_val > y_val {
(y_val, x_val)
} else {
(x_val, y_val)
};
let x = coord_a / z;
let y = coord_b / z;
let z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, RES) };
let zi = find_interval(RES, |i| z_nodes_slice[i] < z);
let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]);
let x_float = x * (RES - 1) as Float;
let xi = (x_float as usize).min(RES - 2);
let dx = x_float - xi as Float;
let y_float = y * (RES - 1) as Float;
let yi = (y_float as usize).min(RES - 2);
let dy = y_float - yi as Float;
let c000 = self.get_coeffs(c_idx, zi, yi, xi);
let c001 = self.get_coeffs(c_idx, zi, yi, xi + 1);
let c010 = self.get_coeffs(c_idx, zi, yi + 1, xi);
let c011 = self.get_coeffs(c_idx, zi, yi + 1, xi + 1);
let c100 = self.get_coeffs(c_idx, zi + 1, yi, xi);
let c101 = self.get_coeffs(c_idx, zi + 1, yi, xi + 1);
let c110 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi);
let c111 = self.get_coeffs(c_idx, zi + 1, yi + 1, xi + 1);
let c00 = lerp(dx, c000, c001);
let c01 = lerp(dx, c010, c011);
let c10 = lerp(dx, c100, c101);
let c11 = lerp(dx, c110, c111);
let c0 = lerp(dy, c00, c01);
let c1 = lerp(dy, c10, c11);
let c = lerp(dz, c0, c1);
RGBSigmoidPolynomial {
c0: c.c0,
c1: c.c1,
c2: c.c2,
}
}
}

View file

@ -1,4 +1,5 @@
use crate::core::camera::CameraTransform; use crate::core::camera::CameraTransform;
use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
use crate::core::filter::Filter; use crate::core::filter::Filter;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
@ -6,13 +7,12 @@ use crate::core::geometry::{
}; };
use crate::core::interaction::SurfaceInteraction; use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat}; use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat};
use crate::spectra::color::{MatrixMulColor, SRGB, Triplet, white_balance};
use crate::spectra::data::generate_cie_d;
use crate::spectra::{ use crate::spectra::{
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
PiecewiseLinearSpectrum, RGB, RGBColorSpace, SampledSpectrum, SampledWavelengths, Spectrum, PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
SpectrumTrait, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum, get_named_spectrum,
}; };
use crate::utils::AtomicFloat; use crate::utils::AtomicFloat;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
@ -20,7 +20,6 @@ use crate::utils::math::linear_least_squares;
use crate::utils::math::{SquareMatrix, wrap_equal_area_square}; use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
use crate::utils::sampling::VarianceEstimator; use crate::utils::sampling::VarianceEstimator;
use crate::utils::transform::AnimatedTransform; use crate::utils::transform::AnimatedTransform;
use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -30,7 +29,7 @@ pub struct RGBFilm {
pub write_fp16: bool, pub write_fp16: bool,
pub filter_integral: Float, pub filter_integral: Float,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub pixels: Arc<Array2D<RGBPixel>>, pub pixels: Array2D<RGBPixel>,
} }
#[repr(C)] #[repr(C)]
@ -53,7 +52,7 @@ impl RGBFilm {
if sensor_ptr.is_null() { if sensor_ptr.is_null() {
panic!("Film must have a sensor"); panic!("Film must have a sensor");
} }
let sensor = unsafe { &*self.sensor }; let sensor = unsafe { &*sensor_ptr };
let filter_integral = base.filter.integral(); let filter_integral = base.filter.integral();
let sensor_matrix = sensor.xyz_from_sensor_rgb; let sensor_matrix = sensor.xyz_from_sensor_rgb;
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix; let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
@ -75,7 +74,7 @@ impl RGBFilm {
write_fp16, write_fp16,
filter_integral, filter_integral,
output_rgbf_from_sensor_rgb, output_rgbf_from_sensor_rgb,
pixels: Arc::new(pixels_array), pixels: std::sync::Arc::new(pixels_array),
} }
} }
} }
@ -89,12 +88,16 @@ impl RGBFilm {
&mut self.base &mut self.base
} }
#[cfg(not(target_os = "cuda"))] pub fn get_sensor(&self) -> &PixelSensor {
pub fn get_sensor(&self) -> Result<&PixelSensor, String> { #[cfg(not(target_os = "cuda"))]
if self.sensor.is_null() { {
return Err("FilmBase error: PixelSensor pointer is null.".to_string()); if self.sensor.is_null() {
panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
}
} }
Ok(unsafe { &*self.sensor }) unsafe { &*self.sensor }
} }
pub fn add_sample( pub fn add_sample(
@ -259,27 +262,20 @@ impl GBufferFilm {
&mut self.base &mut self.base
} }
#[cfg(not(target_os = "cuda"))] pub fn get_sensor(&self) -> &PixelSensor {
pub fn get_sensor(&self) -> Result<&PixelSensor, String> {
if self.sensor.is_null() {
return Err("FilmBase error: PixelSensor pointer is null.".to_string());
}
Ok(unsafe { &*self.sensor })
}
pub unsafe fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
{ {
if self.sensor.is_null() { if self.sensor.is_null() {
panic!("FilmBase: PixelSensor pointer is null"); panic!(
"FilmBase error: PixelSensor pointer is null. This should have been checked during construction."
);
} }
} }
unsafe { &*self.sensor }
&*self.sensor
} }
pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
let sensor = unsafe { self.get_pixel_sensor() }; let sensor = unsafe { self.get_sensor() };
let mut rgb = sensor.to_sensor_rgb(l, lambda); let mut rgb = sensor.to_sensor_rgb(l, lambda);
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
if m > self.max_component_value { if m > self.max_component_value {
@ -310,7 +306,7 @@ impl GBufferFilm {
} }
pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
let sensor = unsafe { self.get_pixel_sensor() }; let sensor = unsafe { self.get_sensor() };
let sensor_rgb = sensor.to_sensor_rgb(l, lambda); let sensor_rgb = sensor.to_sensor_rgb(l, lambda);
self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb)
} }
@ -467,15 +463,15 @@ pub struct PixelSensor {
pub imaging_ratio: Float, pub imaging_ratio: Float,
} }
#[cfg(not(target_os = "cuda"))]
impl PixelSensor { impl PixelSensor {
const N_SWATCH_REFLECTANCES: usize = 24; const N_SWATCH_REFLECTANCES: usize = 24;
#[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
r: Spectrum, r: Spectrum,
g: Spectrum, g: Spectrum,
b: Spectrum, b: Spectrum,
output_colorspace: RGBColorSpace, output_colorspace: RGBColorSpace,
sensor_illum: Option<Arc<Spectrum>>, sensor_illum: Option<std::sync::Arc<Spectrum>>,
imaging_ratio: Float, imaging_ratio: Float,
swatches: &[Spectrum; 24], swatches: &[Spectrum; 24],
) -> Result<Self, Box<dyn Error>> { ) -> Result<Self, Box<dyn Error>> {
@ -490,11 +486,11 @@ impl PixelSensor {
let r_bar = DenselySampledSpectrum::from_spectrum(&r); let r_bar = DenselySampledSpectrum::from_spectrum(&r);
let g_bar = DenselySampledSpectrum::from_spectrum(&g); let g_bar = DenselySampledSpectrum::from_spectrum(&g);
let b_bar = DenselySampledSpectrum::from_spectrum(&b); let b_bar = DenselySampledSpectrum::from_spectrum(&b);
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES]; let mut rgb_camera = [[0.; 3]; Self::N_SWATCH_REFLECTANCES];
let swatches = Self::get_swatches(); let swatches = Self::get_swatches();
for i in 0..N_SWATCH_REFLECTANCES { for i in 0..Self::N_SWATCH_REFLECTANCES {
let rgb = Self::project_reflectance::<RGB>( let rgb = Self::project_reflectance::<RGB>(
&swatches[i], &swatches[i],
illum, illum,
@ -507,10 +503,10 @@ impl PixelSensor {
} }
} }
let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES]; let mut xyz_output = [[0.; 3]; Self::N_SWATCH_REFLECTANCES];
let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone())); let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone()));
let sensor_white_y = illum.inner_product(cie_y()); let sensor_white_y = illum.inner_product(cie_y());
for i in 0..N_SWATCH_REFLECTANCES { for i in 0..Self::N_SWATCH_REFLECTANCES {
let s = swatches[i].clone(); let s = swatches[i].clone();
let xyz = Self::project_reflectance::<XYZ>( let xyz = Self::project_reflectance::<XYZ>(
&s, &s,
@ -537,7 +533,7 @@ impl PixelSensor {
pub fn new_with_white_balance( pub fn new_with_white_balance(
output_colorspace: &RGBColorSpace, output_colorspace: &RGBColorSpace,
sensor_illum: Option<Arc<Spectrum>>, sensor_illum: Option<std::sync::Arc<Spectrum>>,
imaging_ratio: Float, imaging_ratio: Float,
) -> Self { ) -> Self {
let r_bar = DenselySampledSpectrum::from_spectrum(cie_x()); let r_bar = DenselySampledSpectrum::from_spectrum(cie_x());
@ -562,13 +558,16 @@ impl PixelSensor {
} }
} }
pub fn project_reflectance<T: Triplet>( pub fn project_reflectance<T>(
refl: &Spectrum, refl: &Spectrum,
illum: &Spectrum, illum: &Spectrum,
b1: &Spectrum, b1: &Spectrum,
b2: &Spectrum, b2: &Spectrum,
b3: &Spectrum, b3: &Spectrum,
) -> T { ) -> T
where
T: From<[Float; 3]>,
{
let mut result = [0.; 3]; let mut result = [0.; 3];
let mut g_integral = 0.; let mut g_integral = 0.;
@ -590,7 +589,7 @@ impl PixelSensor {
result[2] *= inv_g; result[2] *= inv_g;
} }
T::from_triplet(result[0], result[1], result[2]) T::from((result[0], result[1], result[2]))
} }
} }
@ -652,7 +651,7 @@ pub enum Film {
} }
impl Film { impl Film {
fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
match self { match self {
Film::RGB(f) => f.base(), Film::RGB(f) => f.base(),
Film::GBuffer(f) => f.base(), Film::GBuffer(f) => f.base(),
@ -660,7 +659,7 @@ impl Film {
} }
} }
pub fn base(&mut self) -> &mut FilmBase { pub fn base_mut(&mut self) -> &mut FilmBase {
match self { match self {
Film::RGB(f) => f.base_mut(), Film::RGB(f) => f.base_mut(),
Film::GBuffer(f) => f.base_mut(), Film::GBuffer(f) => f.base_mut(),

View file

@ -1,5 +1,6 @@
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::filters::*;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc}; use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
@ -12,13 +13,13 @@ pub struct FilterSample {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FilterSampler { pub struct FilterSampler {
domain: Bounds2f, pub domain: Bounds2f,
distrib: PiecewiseConstant2D, pub distrib: PiecewiseConstant2D,
f: Array2D<Float>, pub f: Array2D<Float>,
} }
#[cfg(not(target_os = "cuda"))]
impl FilterSampler { impl FilterSampler {
#[cfg(not(target_os = "cuda"))]
pub fn new<F>(radius: Vector2f, func: F) -> Self pub fn new<F>(radius: Vector2f, func: F) -> Self
where where
F: Fn(Point2f) -> Float, F: Fn(Point2f) -> Float,
@ -44,9 +45,7 @@ impl FilterSampler {
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain); let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
Self { domain, f, distrib } Self { domain, f, distrib }
} }
}
impl FilterSampler {
pub fn sample(&self, u: Point2f) -> FilterSample { pub fn sample(&self, u: Point2f) -> FilterSample {
let (p, pdf, pi) = self.distrib.sample(u); let (p, pdf, pi) = self.distrib.sample(u);
@ -59,6 +58,15 @@ impl FilterSampler {
} }
} }
pub trait FilterTrait {
fn radius(&self) -> Vector2f;
fn evaluate(&self, p: Point2f) -> Float;
fn integral(&self) -> Float;
fn sample(&self, u: Point2f) -> FilterSample;
}
#[repr(C)]
#[enum_dispatch(FilterTrait)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Filter { pub enum Filter {
Box(BoxFilter), Box(BoxFilter),
@ -67,44 +75,3 @@ pub enum Filter {
LanczosSinc(LanczosSincFilter), LanczosSinc(LanczosSincFilter),
Triangle(TriangleFilter), Triangle(TriangleFilter),
} }
impl Filter {
pub fn radius(&self) -> Vector2f {
match self {
Filter::Box(f) => f.radius(),
Filter::Gaussian(f) => f.radius(),
Filter::Mitchell(f) => f.radius(),
Filter::LanczosSinc(f) => f.radius(),
Filter::Triangle(f) => f.radius(),
}
}
pub fn evaluate(&self, p: Point2f) -> Float {
match self {
Filter::Box(f) => f.evaluate(p),
Filter::Gaussian(f) => f.evaluate(p),
Filter::Mitchell(f) => f.evaluate(p),
Filter::LanczosSinc(f) => f.evaluate(p),
Filter::Triangle(f) => f.evaluate(p),
}
}
pub fn integral(&self) -> Float {
match self {
Filter::Box(f) => f.integral(),
Filter::Gaussian(f) => f.integral(),
Filter::Mitchell(f) => f.integral(),
Filter::LanczosSinc(f) => f.integral(),
Filter::Triangle(f) => f.integral(),
}
}
pub fn sample(&self, u: Point2f) -> FilterSample {
match self {
Filter::Box(f) => f.sample(u),
Filter::Gaussian(f) => f.sample(u),
Filter::Mitchell(f) => f.sample(u),
Filter::LanczosSinc(f) => f.sample(u),
Filter::Triangle(f) => f.sample(u),
}
}
}

View file

@ -2,16 +2,16 @@ use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::utils::math::{next_float_down, next_float_up}; use crate::utils::math::{next_float_down, next_float_up};
use std::sync::Arc;
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Ray { pub struct Ray {
pub o: Point3f, pub o: Point3f,
pub d: Vector3f, pub d: Vector3f,
pub medium: Option<Arc<Medium>>, pub medium: *const Medium,
pub time: Float, pub time: Float,
// We do this instead of creating a trait for Rayable or some gnarly thing like that // We do this instead of creating a trait for Rayable or some gnarly thing like that
pub differential: Option<RayDifferential>, pub differential: *const RayDifferential,
} }
impl Default for Ray { impl Default for Ray {
@ -27,7 +27,7 @@ impl Default for Ray {
} }
impl Ray { impl Ray {
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: Option<Arc<Medium>>) -> Self { pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: *const Medium) -> Self {
Self { Self {
o, o,
d, d,
@ -110,6 +110,7 @@ impl Ray {
} }
} }
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub struct RayDifferential { pub struct RayDifferential {
pub rx_origin: Point3f, pub rx_origin: Point3f,

View file

@ -1,9 +1,10 @@
use crate::camera::{Camera, CameraTrait};
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF}; use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF};
use crate::core::camera::Camera;
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
}; };
use crate::core::light::Light;
use crate::core::material::{ use crate::core::material::{
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
}; };
@ -11,26 +12,24 @@ use crate::core::medium::{Medium, MediumInterface, PhaseFunction};
use crate::core::options::get_options; use crate::core::options::get_options;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::sampler::{Sampler, SamplerTrait};
use crate::core::texture::{FloatTexture, UniversalTextureEvaluator}; use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator};
use crate::image::Image; use crate::images::Image;
use crate::lights::{Light, LightTrait};
use crate::shapes::Shape; use crate::shapes::Shape;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{clamp, difference_of_products, square}; use crate::utils::math::{clamp, difference_of_products, square};
use bumpalo::Bump;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::any::Any; use std::any::Any;
use std::sync::Arc;
#[derive(Default, Clone, Debug)] #[repr(C)]
#[derive(Default, Copy, Clone, Debug)]
pub struct InteractionData { pub struct InteractionData {
pub pi: Point3fi, pub pi: Point3fi,
pub n: Normal3f, pub n: Normal3f,
pub time: Float, pub time: Float,
pub wo: Vector3f, pub wo: Vector3f,
pub medium_interface: Option<MediumInterface>, pub medium_interface: MediumInterface,
pub medium: Option<Arc<Medium>>, pub medium: *const Medium,
} }
#[enum_dispatch] #[enum_dispatch]
@ -63,16 +62,16 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
false false
} }
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> { fn get_medium(&self, w: Vector3f) -> *const Medium {
let data = self.get_common(); let data = self.get_common();
if let Some(mi) = &data.medium_interface { if let Some(mi) = &data.medium_interface {
if w.dot(data.n.into()) > 0.0 { if w.dot(data.n.into()) > 0.0 {
mi.outside.clone() mi.outside
} else { } else {
mi.inside.clone() mi.inside
} }
} else { } else {
data.medium.clone() data.medium
} }
} }
@ -91,9 +90,8 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
ray ray
} }
fn spawn_ray_to_interaction(&self, other: &dyn InteractionTrait) -> Ray { fn spawn_ray_to_interaction(&self, other: InteractionData) -> Ray {
let data = self.get_common(); let data = self.get_common();
let other_data = other.get_common();
let mut ray = let mut ray =
Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n); Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n);
@ -110,8 +108,9 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug {
} }
} }
#[repr(C)]
#[enum_dispatch(InteractionTrait)] #[enum_dispatch(InteractionTrait)]
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Interaction { pub enum Interaction {
Surface(SurfaceInteraction), Surface(SurfaceInteraction),
Medium(MediumInteraction), Medium(MediumInteraction),
@ -128,6 +127,7 @@ impl Interaction {
} }
} }
#[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SimpleInteraction { pub struct SimpleInteraction {
pub common: InteractionData, pub common: InteractionData,
@ -171,8 +171,9 @@ impl InteractionTrait for SimpleInteraction {
} }
} }
#[derive(Default, Clone, Debug)] #[repr(C)]
pub struct Shadinggeom { #[derive(Default, Clone, Copy, Debug)]
pub struct ShadingGeom {
pub n: Normal3f, pub n: Normal3f,
pub dpdu: Vector3f, pub dpdu: Vector3f,
pub dpdv: Vector3f, pub dpdv: Vector3f,
@ -180,7 +181,8 @@ pub struct Shadinggeom {
pub dndv: Normal3f, pub dndv: Normal3f,
} }
#[derive(Debug, Default, Clone)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct SurfaceInteraction { pub struct SurfaceInteraction {
pub common: InteractionData, pub common: InteractionData,
pub uv: Point2f, pub uv: Point2f,
@ -188,19 +190,22 @@ pub struct SurfaceInteraction {
pub dpdv: Vector3f, pub dpdv: Vector3f,
pub dndu: Normal3f, pub dndu: Normal3f,
pub dndv: Normal3f, pub dndv: Normal3f,
pub shading: Shadinggeom, pub shading: ShadingGeom,
pub face_index: usize, pub face_index: u32,
pub area_light: Option<Arc<Light>>, pub area_light: *const Light,
pub material: Option<Arc<Material>>, pub material: *const Material,
pub shape: *const Shape,
pub dpdx: Vector3f, pub dpdx: Vector3f,
pub dpdy: Vector3f, pub dpdy: Vector3f,
pub dudx: Float, pub dudx: Float,
pub dvdx: Float, pub dvdx: Float,
pub dudy: Float, pub dudy: Float,
pub dvdy: Float, pub dvdy: Float,
pub shape: Arc<Shape>,
} }
unsafe impl Send for SurfaceInteraction {}
unsafe impl Sync for SurfaceInteraction {}
impl SurfaceInteraction { impl SurfaceInteraction {
pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if let Some(area_light) = &self.area_light { if let Some(area_light) = &self.area_light {
@ -304,6 +309,7 @@ impl SurfaceInteraction {
} }
} }
#[cfg(not(target_os = "cuda"))]
pub fn get_bsdf( pub fn get_bsdf(
&mut self, &mut self,
r: &Ray, r: &Ray,
@ -343,12 +349,12 @@ impl SurfaceInteraction {
Some(bsdf) Some(bsdf)
} }
#[cfg(not(target_os = "cuda"))]
pub fn get_bssrdf( pub fn get_bssrdf(
&self, &self,
_ray: &Ray, _ray: &Ray,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
_camera: &Camera, _camera: &Camera,
_scratch: &Bump,
) -> Option<BSSRDF> { ) -> Option<BSSRDF> {
let material = { let material = {
let root_mat = self.material.as_deref()?; let root_mat = self.material.as_deref()?;
@ -370,8 +376,8 @@ impl SurfaceInteraction {
fn compute_bump_geom( fn compute_bump_geom(
&mut self, &mut self,
tex_eval: &UniversalTextureEvaluator, tex_eval: &UniversalTextureEvaluator,
displacement: Option<FloatTexture>, displacement: *const GPUFloatTexture,
normal_image: Option<Arc<Image>>, normal_image: *const Image,
) { ) {
let ctx = NormalBumpEvalContext::from(&*self); let ctx = NormalBumpEvalContext::from(&*self);
let (dpdu, dpdv) = if let Some(disp) = displacement { let (dpdu, dpdv) = if let Some(disp) = displacement {
@ -494,12 +500,12 @@ impl InteractionTrait for SurfaceInteraction {
&mut self.common &mut self.common
} }
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> { fn get_medium(&self, w: Vector3f) -> *const Medium {
self.common.medium_interface.as_ref().and_then(|interface| { self.common.medium_interface.as_ref().and_then(|interface| {
if self.n().dot(w.into()) > 0.0 { if self.n().dot(w.into()) > 0.0 {
interface.outside.clone() interface.outside
} else { } else {
interface.inside.clone() interface.inside
} }
}) })
} }
@ -543,7 +549,7 @@ impl SurfaceInteraction {
dpdv, dpdv,
dndu, dndu,
dndv, dndv,
shading: Shadinggeom { shading: ShadingGeom {
n: shading_n, n: shading_n,
dpdu, dpdu,
dpdv, dpdv,
@ -559,7 +565,7 @@ impl SurfaceInteraction {
dudy: 0.0, dudy: 0.0,
dvdx: 0.0, dvdx: 0.0,
dvdy: 0.0, dvdy: 0.0,
shape: Arc::new(Shape::default()), shape: core::ptr::null(),
} }
} }
@ -625,31 +631,32 @@ impl SurfaceInteraction {
} }
} }
#[cfg(not(target_os = "cuda"))]
pub fn set_intersection_properties( pub fn set_intersection_properties(
&mut self, &mut self,
mtl: Arc<Material>, mtl: *const Material,
area: Arc<Light>, area: *const Light,
prim_medium_interface: Option<MediumInterface>, prim_medium_interface: MediumInterface,
ray_medium: Arc<Medium>, ray_medium: *const Medium,
) { ) {
self.material = Some(mtl); self.material = mtl;
self.area_light = Some(area); self.area_light = area;
if prim_medium_interface
.as_ref() if prim_medium_interface.is_medium_transition() {
.is_some_and(|mi| mi.is_medium_transition()) self.common.medium_interface = *prim_medium_interface;
{
self.common.medium_interface = prim_medium_interface;
} else { } else {
self.common.medium = Some(ray_medium); self.common.medium = ray_medium;
} }
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MediumInteraction { pub struct MediumInteraction {
pub common: InteractionData, pub common: InteractionData,
pub medium: Arc<Medium>, pub medium: *const Medium,
pub phase: PhaseFunction, pub phase: PhaseFunction,
pub medium_interface: MediumInterface,
} }
impl MediumInteraction { impl MediumInteraction {
@ -657,7 +664,7 @@ impl MediumInteraction {
p: Point3f, p: Point3f,
wo: Vector3f, wo: Vector3f,
time: Float, time: Float,
medium: Arc<Medium>, medium: *const Medium,
phase: PhaseFunction, phase: PhaseFunction,
) -> Self { ) -> Self {
Self { Self {
@ -667,10 +674,11 @@ impl MediumInteraction {
time, time,
wo: wo.normalize(), wo: wo.normalize(),
medium_interface: None, medium_interface: None,
medium: Some(medium.clone()), medium,
}, },
medium, medium,
phase, phase,
medium_interface: MediumInterface::empty(),
} }
} }
} }

View file

@ -1,38 +1,30 @@
use crate::core::color::RGB;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
Vector3f, VectorLike, cos_theta, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction, Interaction, InteractionData, InteractionTrait, MediumInteraction, SimpleInteraction,
SurfaceInteraction,
}; };
use crate::core::medium::MediumInterface; use crate::core::medium::MediumInterface;
use crate::core::pbrt::{Float, PI}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::images::Image; use crate::images::Image;
use crate::lights::*;
use crate::spectra::{ use crate::spectra::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum,
SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::containers::InternCache; use crate::utils::Transform;
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square}; use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::transform::TransformGeneric; use crate::{Float, PI};
use bitflags::bitflags; use bitflags::bitflags;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::{Arc, OnceLock};
use crate::lights::diffuse::DiffuseAreaLight;
use crate::lights::infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
use crate::lights::sampler::{LightSampler, LightSamplerTrait};
static SPECTRUM_CACHE: OnceLock<InternCache<DenselySampledSpectrum>> = OnceLock::new();
fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
SPECTRUM_CACHE.get_or_init(InternCache::new)
}
bitflags! { bitflags! {
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LightType: u32 { pub struct LightType: u32 {
const DeltaPosition = 1; const DeltaPosition = 1;
@ -52,15 +44,37 @@ impl LightType {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct LightLeSample { pub struct LightLeSample {
l: SampledSpectrum, pub l: SampledSpectrum,
ray: Ray, pub ray: Ray,
intr: Option<Interaction>, pub intr: *const InteractionData,
pdf_pos: Float, pub pdf_pos: Float,
pdf_dir: Float, pub pdf_dir: Float,
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct LightLiSample {
pub l: SampledSpectrum,
pub wi: Vector3f,
pub pdf: Float,
pub p_light: InteractionData,
}
#[cfg(not(target_os = "cuda"))]
impl LightLiSample {
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: InteractionData) -> Self {
Self {
l,
wi,
pdf,
p_light,
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Default, Clone)] #[derive(Debug, Copy, Default, Clone)]
pub struct LightSampleContext { pub struct LightSampleContext {
pub pi: Point3fi, pub pi: Point3fi,
@ -122,38 +136,19 @@ impl From<&Interaction> for LightSampleContext {
} }
} }
#[derive(Debug, Clone)]
pub struct LightLiSample {
pub l: SampledSpectrum,
pub wi: Vector3f,
pub pdf: Float,
pub p_light: Arc<Interaction>,
}
impl LightLiSample {
pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self {
Self {
l,
wi,
pdf,
p_light: Arc::new(p_light),
}
}
}
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct LightBaseData { pub struct LightBase {
pub render_from_light: Transform, pub render_from_light: Transform,
pub light_type: u32, pub light_type: u32,
pub mi_inside: i32, pub medium_interface: MediumInterface,
pub mi_outside: i32,
} }
#[cfg(not(target_os = "cuda"))]
impl LightBase { impl LightBase {
pub fn new( pub fn new(
light_type: LightType, light_type: LightType,
render_from_light: &TransformGeneric<Float>, render_from_light: &Transform,
medium_interface: &MediumInterface, medium_interface: &MediumInterface,
) -> Self { ) -> Self {
Self { Self {
@ -163,6 +158,14 @@ impl LightBase {
} }
} }
pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrum {
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
cache.lookup(dense_spectrum).as_ref()
}
}
impl LightBase {
fn l( fn l(
&self, &self,
_p: Point3f, _p: Point3f,
@ -181,24 +184,20 @@ impl LightBase {
pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
SampledSpectrum::default() SampledSpectrum::default()
} }
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new);
let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
cache.lookup(dense_spectrum)
}
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct LightBounds { pub struct LightBounds {
bounds: Bounds3f, pub bounds: Bounds3f,
phi: Float, pub phi: Float,
w: Vector3f, pub w: Vector3f,
cos_theta_o: Float, pub cos_theta_o: Float,
cos_theta_e: Float, pub cos_theta_e: Float,
two_sided: bool, pub two_sided: bool,
} }
#[cfg(not(target_os = "cuda"))]
impl LightBounds { impl LightBounds {
pub fn new( pub fn new(
bounds: &Bounds3f, bounds: &Bounds3f,
@ -217,7 +216,9 @@ impl LightBounds {
two_sided, two_sided,
} }
} }
}
impl LightBounds {
pub fn centroid(&self) -> Point3f { pub fn centroid(&self) -> Point3f {
self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2. self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2.
} }
@ -301,9 +302,9 @@ impl LightBounds {
} }
#[enum_dispatch] #[enum_dispatch]
pub trait LightTrait: Send + Sync + std::fmt::Debug { pub trait LightTrait {
fn base(&self) -> &LightBase; fn base(&self) -> &LightBase;
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
fn sample_li( fn sample_li(
&self, &self,
ctx: &LightSampleContext, ctx: &LightSampleContext,
@ -311,7 +312,9 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug {
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
allow_incomplete_pdf: bool, allow_incomplete_pdf: bool,
) -> Option<LightLiSample>; ) -> Option<LightLiSample>;
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float; fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float;
fn l( fn l(
&self, &self,
p: Point3f, p: Point3f,
@ -320,16 +323,26 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug {
w: Vector3f, w: Vector3f,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum; ) -> SampledSpectrum;
fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum; fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum;
fn preprocess(&mut self, scene_bounds: &Bounds3f);
fn bounds(&self) -> Option<LightBounds>;
fn light_type(&self) -> LightType { fn light_type(&self) -> LightType {
self.base().light_type() self.base().light_type
} }
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds>;
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, scene_bounds: &Bounds3f);
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum;
} }
#[derive(Debug, Clone)]
#[enum_dispatch(LightTrait)] #[enum_dispatch(LightTrait)]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum Light { pub enum Light {
DiffuseArea(DiffuseAreaLight), DiffuseArea(DiffuseAreaLight),
@ -342,548 +355,3 @@ pub enum Light {
Projection(ProjectionLight), Projection(ProjectionLight),
Spot(SpotLight), Spot(SpotLight),
} }
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DistantLightData {
pub base: LightBaseData,
pub lemit_coeffs: [Float; 32],
pub scale: Float,
pub scene_center: Point3f,
pub scene_radius: Float,
}
impl DistantLightData {
pub fn sample_li(
&self,
ctx_p: Point3f,
lambda: &SampledWavelengths,
) -> (SampledSpectrum, Vector3f, Float, Point3f) {
let wi = self
.base
.render_from_light
.apply_to_vector(Vector3f::new(0., 0., 1.))
.normalize();
let p_outside = ctx_p + wi * 2. * self.scene_radius;
let spectrum = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
let li = self.scale * spectrum.sample(lambda);
(li, wi, 1.0, p_outside)
}
}
impl LightTrait for DistantLight {
fn base(&self) -> &LightBase {
&self.base
}
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
self.scale * self.lemit.sample(&lambda) * PI * self.scene_radius.sqrt()
}
fn sample_li(
&self,
ctx: &LightSampleContext,
_u: Point2f,
lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool,
) -> Option<LightLiSample> {
let wi = self
.base
.render_from_light
.apply_to_vector(Vector3f::new(0., 0., 1.))
.normalize();
let p_outside = ctx.p() + wi * 2. * self.scene_radius;
let li = self.scale * self.lemit.sample(lambda);
let intr = SimpleInteraction::new(
Point3fi::new_from_point(p_outside),
0.0,
Some(self.base.medium_interface.clone()),
);
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
}
fn pdf_li(
&self,
_ctx: &LightSampleContext,
_wi: Vector3f,
_allow_incomplete_pdf: bool,
) -> Float {
0.
}
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn preprocess(&mut self, scene_bounds: &Bounds3f) {
let (center, radius) = scene_bounds.bounding_sphere();
self.scene_center = center;
self.scene_radius = radius;
}
fn bounds(&self) -> Option<LightBounds> {
None
}
}
#[derive(Debug, Clone)]
pub struct GoniometricLight {
pub base: LightBase,
iemit: Arc<DenselySampledSpectrum>,
scale: Float,
image: Image,
distrib: PiecewiseConstant2D,
}
impl GoniometricLight {
pub fn new(
render_from_light: &TransformGeneric<Float>,
medium_interface: &MediumInterface,
iemit: Spectrum,
scale: Float,
image: Image,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
render_from_light,
medium_interface,
);
let i_interned = LightBase::lookup_spectrum(&iemit);
let d = image.get_sampling_distribution_uniform();
let distrib = PiecewiseConstant2D::new_with_data(&d);
Self {
base,
iemit: i_interned,
scale,
image,
distrib,
}
}
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
let uv = equal_area_sphere_to_square(w);
self.scale * self.iemit.sample(lambda) * self.image.lookup_nearest_channel(uv, 0)
}
}
impl LightTrait for GoniometricLight {
fn base(&self) -> &LightBase {
&self.base
}
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum_y = 0.;
for y in 0..self.image.resolution.y() {
for x in 0..self.image.resolution.x() {
sum_y += self.image.get_channel(Point2i::new(x, y), 0);
}
}
self.scale * self.iemit.sample(&lambda) * 4. * PI * sum_y
/ (self.image.resolution.x() * self.image.resolution.y()) as Float
}
fn sample_li(
&self,
_ctx: &LightSampleContext,
_u: Point2f,
_lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool,
) -> Option<LightLiSample> {
todo!()
}
fn pdf_li(
&self,
_ctx: &LightSampleContext,
_wi: Vector3f,
_allow_incomplete_pdf: bool,
) -> Float {
0.
}
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
fn bounds(&self) -> Option<LightBounds> {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct PointLightData {
pub base: LightBaseData,
pub i_coeffs: [Float; 32],
pub scale: Float,
}
impl PointLightData {
pub fn sample_li(
&self,
ctx_p: Point3f,
lambda: &SampledWavelengths,
) -> (SampledSpectrum, Vector3f, Float, Point3fi) {
let pi = self
.base
.render_from_light
.apply_to_interval(&Point3fi::default());
let p: Point3f = pi.into();
let wi = (p - ctx_p).normalize();
let spectrum = DenselySampledSpectrum::from_array(&self.i_coeffs);
let li = self.scale * spectrum.sample(lambda) / p.distance_squared(ctx_p);
(li, wi, 1.0, pi)
}
}
impl LightTrait for PointLight {
fn base(&self) -> &LightBase {
&self.base
}
fn sample_li(
&self,
ctx: &LightSampleContext,
_u: Point2f,
lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool,
) -> Option<LightLiSample> {
let pi = self
.base
.render_from_light
.apply_to_interval(&Point3fi::default());
let p: Point3f = pi.into();
let wi = (p - ctx.p()).normalize();
let li = self.scale * self.i.sample(lambda) / p.distance_squared(ctx.p());
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
}
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * self.scale * self.i.sample(&lambda)
}
fn pdf_li(
&self,
_ctx: &LightSampleContext,
_wi: Vector3f,
_allow_incomplete_pdf: bool,
) -> Float {
0.
}
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
fn bounds(&self) -> Option<LightBounds> {
let p = self
.base
.render_from_light
.apply_to_point(Point3f::new(0., 0., 0.));
let phi = 4. * PI * self.scale * self.i.max_value();
Some(LightBounds::new(
&Bounds3f::from_points(p, p),
Vector3f::new(0., 0., 1.),
phi,
PI.cos(),
(PI / 2.).cos(),
false,
))
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ProjectionLightPOD {
pub base: LightBasePOD,
pub scale: Float,
pub hither: Float,
pub screen_from_light: Transform,
pub screen_bounds: Bounds2f,
pub image_id: u32,
}
impl ProjectionLight {
pub fn new(
render_from_light: TransformGeneric<Float>,
medium_interface: MediumInterface,
image: Image,
image_color_space: Arc<RGBColorSpace>,
scale: Float,
fov: Float,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
&render_from_light,
&medium_interface,
);
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
let screen_bounds = if aspect > 1. {
Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
} else {
Bounds2f::from_points(
Point2f::new(-1., 1. / aspect),
Point2f::new(1., 1. / aspect),
)
};
let hither = 1e-3;
let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap();
let light_from_screen = screen_from_light.inverse();
let opposite = (radians(fov) / 2.).tan();
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
let a = 4. * square(opposite) * aspect_ratio;
let dwda = |p: Point2f| {
let w =
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.)));
cos_theta(w.normalize()).powi(3)
};
let d = image.get_sampling_distribution(dwda, screen_bounds);
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
Self {
base,
image,
image_color_space,
screen_bounds,
screen_from_light,
light_from_screen,
scale,
hither,
a,
distrib,
}
}
pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum {
if w.z() < self.hither {
return SampledSpectrum::new(0.);
}
let ps = self.screen_from_light.apply_to_point(w.into());
if !self.screen_bounds.contains(Point2f::new(ps.x(), ps.y())) {
return SampledSpectrum::new(0.);
}
let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y())));
let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c);
}
let s = RGBIlluminantSpectrum::new(self.image_color_space.as_ref(), RGB::clamp_zero(rgb));
self.scale * s.sample(&lambda)
}
}
impl LightTrait for ProjectionLight {
fn base(&self) -> &LightBase {
&self.base
}
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum = SampledSpectrum::new(0.);
for y in 0..self.image.resolution.y() {
for x in 0..self.image.resolution.x() {
let ps = self.screen_bounds.lerp(Point2f::new(
(x as Float + 0.5) / self.image.resolution.x() as Float,
(y as Float + 0.5) / self.image.resolution.y() as Float,
));
let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new(
ps.x(),
ps.y(),
0.,
)));
let w = w_raw.normalize();
let dwda = cos_theta(w).powi(3);
let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = self.image.get_channel(Point2i::new(x, y), c);
}
let s = RGBIlluminantSpectrum::new(
self.image_color_space.as_ref(),
RGB::clamp_zero(rgb),
);
sum += s.sample(&lambda) * dwda;
}
}
self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float
}
fn sample_li(
&self,
_ctx: &LightSampleContext,
_u: Point2f,
_lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool,
) -> Option<LightLiSample> {
todo!()
}
fn pdf_li(
&self,
_ctx: &LightSampleContext,
_wi: Vector3f,
_allow_incomplete_pdf: bool,
) -> Float {
todo!()
}
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
fn bounds(&self) -> Option<LightBounds> {
todo!()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SpotLightData {
pub base: LightBaseData,
pub iemit_coeffs: [Float; 32],
pub scale: Float,
pub cos_falloff_start: Float,
pub cos_falloff_end: Float,
}
impl SpotLightData {
pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
let cos_theta = w.z(); // assuming normalized in light space
let falloff = crate::utils::math::smooth_step(
cos_theta,
self.cos_falloff_end,
self.cos_falloff_start,
);
let spectrum = DenselySampledSpectrum::from_array(&self.iemit_coeffs);
falloff * self.scale * spectrum.sample(lambda)
}
}
impl LightTrait for SpotLight {
fn base(&self) -> &LightBase {
&self.base
}
fn sample_li(
&self,
ctx: &LightSampleContext,
_u: Point2f,
lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool,
) -> Option<LightLiSample> {
let pi = self
.base
.render_from_light
.apply_to_interval(&Point3fi::default());
let p: Point3f = pi.into();
let wi = (p - ctx.p()).normalize();
let w_light = self.base.render_from_light.apply_inverse_vector(-wi);
let li = self.i(w_light, *lambda) / p.distance_squared(ctx.p());
let intr = SimpleInteraction::new(pi, 0.0, Some(self.base.medium_interface.clone()));
Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr)))
}
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
self.scale
* self.iemit.sample(&lambda)
* 2.
* PI
* ((1. - self.cos_fallof_start) + (self.cos_fallof_start - self.cos_fallof_end) / 2.)
}
fn pdf_li(
&self,
_ctx: &LightSampleContext,
_wi: Vector3f,
_allow_incomplete_pdf: bool,
) -> Float {
0.
}
fn l(
&self,
_p: Point3f,
_n: Normal3f,
_uv: Point2f,
_w: Vector3f,
_lambda: &SampledWavelengths,
) -> SampledSpectrum {
todo!()
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!()
}
fn bounds(&self) -> Option<LightBounds> {
let p = self
.base
.render_from_light
.apply_to_point(Point3f::default());
let w = self
.base
.render_from_light
.apply_to_vector(Vector3f::new(0., 0., 1.))
.normalize();
let phi = self.scale * self.iemit.max_value() * 4. * PI;
let cos_theta_e = (self.cos_fallof_end.acos() - self.cos_fallof_start.acos()).cos();
Some(LightBounds::new(
&Bounds3f::from_points(p, p),
w,
phi,
self.cos_fallof_start,
cos_theta_e,
false,
))
}
}

View file

@ -1,4 +1,3 @@
use bumpalo::Bump;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
@ -9,18 +8,21 @@ use crate::core::bxdf::{
}; };
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike}; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::core::interaction::InteractionTrait; use crate::core::interaction::InteractionTrait;
use crate::core::interaction::{Interaction, Shadinggeom, SurfaceInteraction}; use crate::core::interaction::{Interaction, ShadingGeom, SurfaceInteraction};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::core::texture::{ use crate::core::texture::{
FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator, GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator,
}; };
use crate::image::{Image, WrapMode, WrapMode2D}; use crate::images::{Image, WrapMode, WrapMode2D};
use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::math::clamp; use crate::utils::math::clamp;
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct MaterialEvalContext { pub struct MaterialEvalContext {
pub texture: TextureEvalContext, pub texture: TextureEvalContext,
pub wo: Vector3f, pub wo: Vector3f,
@ -47,20 +49,21 @@ impl From<&SurfaceInteraction> for MaterialEvalContext {
} }
} }
#[repr(C)]
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct NormalBumpEvalContext { pub struct NormalBumpEvalContext {
p: Point3f, pub p: Point3f,
uv: Point2f, pub uv: Point2f,
n: Normal3f, pub n: Normal3f,
shading: Shadinggeom, pub shading: ShadingGeom,
dpdx: Vector3f, pub dpdx: Vector3f,
dpdy: Vector3f, pub dpdy: Vector3f,
// All 0 // All 0
dudx: Float, pub dudx: Float,
dudy: Float, pub dudy: Float,
dvdx: Float, pub dvdx: Float,
dvdy: Float, pub dvdy: Float,
face_index: usize, pub face_index: usize,
} }
impl From<&SurfaceInteraction> for NormalBumpEvalContext { impl From<&SurfaceInteraction> for NormalBumpEvalContext {
@ -117,7 +120,7 @@ pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f,
pub fn bump_map<T: TextureEvaluator>( pub fn bump_map<T: TextureEvaluator>(
tex_eval: &T, tex_eval: &T,
displacement: &FloatTexture, displacement: &GPUFloatTexture,
ctx: &NormalBumpEvalContext, ctx: &NormalBumpEvalContext,
) -> (Vector3f, Vector3f) { ) -> (Vector3f, Vector3f) {
debug_assert!(tex_eval.can_evaluate(&[displacement], &[])); debug_assert!(tex_eval.can_evaluate(&[displacement], &[]));
@ -168,12 +171,12 @@ pub trait MaterialTrait: Send + Sync + std::fmt::Debug {
) -> Option<BSSRDF>; ) -> Option<BSSRDF>;
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool; fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
fn get_normal_map(&self) -> Option<Arc<Image>>; fn get_normal_map(&self) -> *const Image;
fn get_displacement(&self) -> Option<FloatTexture>; fn get_displacement(&self) -> Option<GPUFloatTexture>;
fn has_surface_scattering(&self) -> bool; fn has_surface_scattering(&self) -> bool;
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
#[enum_dispatch(MaterialTrait)] #[enum_dispatch(MaterialTrait)]
pub enum Material { pub enum Material {
CoatedDiffuse(CoatedDiffuseMaterial), CoatedDiffuse(CoatedDiffuseMaterial),
@ -191,32 +194,33 @@ pub enum Material {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CoatedDiffuseMaterial { pub struct CoatedDiffuseMaterial {
displacement: FloatTexture, pub displacement: GPUFloatTexture,
normal_map: Option<Arc<Image>>, pub normal_map: *const Image,
reflectance: SpectrumTexture, pub reflectance: GPUSpectrumTexture,
albedo: SpectrumTexture, pub albedo: GPUSpectrumTexture,
u_roughness: FloatTexture, pub u_roughness: GPUFloatTexture,
v_roughness: FloatTexture, pub v_roughness: GPUFloatTexture,
thickness: FloatTexture, pub thickness: GPUFloatTexture,
g: FloatTexture, pub g: GPUFloatTexture,
eta: Spectrum, pub eta: Spectrum,
remap_roughness: bool, pub remap_roughness: bool,
max_depth: usize, pub max_depth: usize,
n_samples: usize, pub n_samples: usize,
} }
impl CoatedDiffuseMaterial { impl CoatedDiffuseMaterial {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
reflectance: SpectrumTexture, reflectance: GPUSpectrumTexture,
u_roughness: FloatTexture, u_roughness: GPUFloatTexture,
v_roughness: FloatTexture, v_roughness: GPUFloatTexture,
thickness: FloatTexture, thickness: GPUFloatTexture,
albedo: SpectrumTexture, albedo: GPUSpectrumTexture,
g: FloatTexture, g: GPUFloatTexture,
eta: Spectrum, eta: Spectrum,
displacement: FloatTexture, displacement: GPUFloatTexture,
normal_map: Option<Arc<Image>>, normal_map: *const Image,
remap_roughness: bool, remap_roughness: bool,
max_depth: usize, max_depth: usize,
n_samples: usize, n_samples: usize,
@ -313,8 +317,8 @@ impl MaterialTrait for CoatedDiffuseMaterial {
) )
} }
fn get_normal_map(&self) -> Option<Arc<Image>> { fn get_normal_map(&self) -> *const Image {
self.normal_map.clone() self.normal_map
} }
fn get_displacement(&self) -> Option<FloatTexture> { fn get_displacement(&self) -> Option<FloatTexture> {
@ -326,10 +330,11 @@ impl MaterialTrait for CoatedDiffuseMaterial {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial { pub struct CoatedConductorMaterial {
displacement: FloatTexture, displacement: FloatTexture,
normal_map: Option<Arc<Image>>, normal_map: *const Image,
interface_uroughness: FloatTexture, interface_uroughness: FloatTexture,
interface_vroughness: FloatTexture, interface_vroughness: FloatTexture,
thickness: FloatTexture, thickness: FloatTexture,
@ -348,6 +353,7 @@ pub struct CoatedConductorMaterial {
impl CoatedConductorMaterial { impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
displacement: FloatTexture, displacement: FloatTexture,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<Image>>,
@ -502,8 +508,8 @@ impl MaterialTrait for CoatedConductorMaterial {
tex_eval.can_evaluate(&float_textures, &spectrum_textures) tex_eval.can_evaluate(&float_textures, &spectrum_textures)
} }
fn get_normal_map(&self) -> Option<Arc<Image>> { fn get_normal_map(&self) -> *const Image {
self.normal_map.clone() self.normal_map
} }
fn get_displacement(&self) -> Option<FloatTexture> { fn get_displacement(&self) -> Option<FloatTexture> {
@ -515,7 +521,8 @@ impl MaterialTrait for CoatedConductorMaterial {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ConductorMaterial; pub struct ConductorMaterial;
impl MaterialTrait for ConductorMaterial { impl MaterialTrait for ConductorMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
@ -537,7 +544,7 @@ impl MaterialTrait for ConductorMaterial {
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!() todo!()
} }
fn get_normal_map(&self) -> Option<Arc<Image>> { fn get_normal_map(&self) -> *const Image {
todo!() todo!()
} }
fn get_displacement(&self) -> Option<FloatTexture> { fn get_displacement(&self) -> Option<FloatTexture> {
@ -547,9 +554,11 @@ impl MaterialTrait for ConductorMaterial {
todo!() todo!()
} }
} }
#[derive(Clone, Debug)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DielectricMaterial { pub struct DielectricMaterial {
normal_map: Option<Arc<Image>>, normal_map: *const Image,
displacement: FloatTexture, displacement: FloatTexture,
u_roughness: FloatTexture, u_roughness: FloatTexture,
v_roughness: FloatTexture, v_roughness: FloatTexture,
@ -612,9 +621,11 @@ impl MaterialTrait for DielectricMaterial {
false false
} }
} }
#[derive(Clone, Debug)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseMaterial { pub struct DiffuseMaterial {
normal_map: Option<Arc<Image>>, normal_map: *const Image,
displacement: FloatTexture, displacement: FloatTexture,
reflectance: SpectrumTexture, reflectance: SpectrumTexture,
} }
@ -656,8 +667,11 @@ impl MaterialTrait for DiffuseMaterial {
false false
} }
} }
#[derive(Clone, Debug)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct DiffuseTransmissionMaterial; pub struct DiffuseTransmissionMaterial;
impl MaterialTrait for DiffuseTransmissionMaterial { impl MaterialTrait for DiffuseTransmissionMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
@ -692,8 +706,11 @@ impl MaterialTrait for DiffuseTransmissionMaterial {
todo!() todo!()
} }
} }
#[derive(Clone, Debug)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HairMaterial; pub struct HairMaterial;
impl MaterialTrait for HairMaterial { impl MaterialTrait for HairMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
&self, &self,
@ -726,7 +743,8 @@ impl MaterialTrait for HairMaterial {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MeasuredMaterial; pub struct MeasuredMaterial;
impl MaterialTrait for MeasuredMaterial { impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
@ -759,7 +777,9 @@ impl MaterialTrait for MeasuredMaterial {
todo!() todo!()
} }
} }
#[derive(Clone, Debug)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct SubsurfaceMaterial; pub struct SubsurfaceMaterial;
impl MaterialTrait for SubsurfaceMaterial { impl MaterialTrait for SubsurfaceMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
@ -792,7 +812,9 @@ impl MaterialTrait for SubsurfaceMaterial {
todo!() todo!()
} }
} }
#[derive(Clone, Debug)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct ThinDielectricMaterial; pub struct ThinDielectricMaterial;
impl MaterialTrait for ThinDielectricMaterial { impl MaterialTrait for ThinDielectricMaterial {
fn get_bsdf<T: TextureEvaluator>( fn get_bsdf<T: TextureEvaluator>(
@ -824,10 +846,12 @@ impl MaterialTrait for ThinDielectricMaterial {
todo!() todo!()
} }
} }
#[derive(Clone, Debug)]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct MixMaterial { pub struct MixMaterial {
pub amount: FloatTexture, pub amount: FloatTexture,
pub materials: [Box<Material>; 2], pub materials: [Ptr<Material>; 2],
} }
impl MixMaterial { impl MixMaterial {
@ -835,23 +859,19 @@ impl MixMaterial {
&self, &self,
tex_eval: &T, tex_eval: &T,
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
) -> &Material { ) -> Option<&Material> {
let amt = tex_eval.evaluate_float(&self.amount, ctx); let amt = tex_eval.evaluate_float(&self.amount, ctx);
if amt <= 0.0 { let index = if amt <= 0.0 {
return &self.materials[0]; 0
} } else if amt >= 1.0 {
if amt >= 1.0 { 1
return &self.materials[1];
}
let u = hash_float(&(ctx.p, ctx.wo));
if amt < u {
&self.materials[0]
} else { } else {
&self.materials[1] let u = hash_float(&(ctx.p, ctx.wo));
} if amt < u { 0 } else { 1 }
};
self.materials[index].get()
} }
} }
@ -862,8 +882,11 @@ impl MaterialTrait for MixMaterial {
ctx: &MaterialEvalContext, ctx: &MaterialEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> BSDF { ) -> BSDF {
let chosen_mat = self.choose_material(tex_eval, ctx); if let Some(mat) = self.choose_material(tex_eval, ctx) {
chosen_mat.get_bsdf(tex_eval, ctx, lambda) mat.get_bsdf(tex_eval, ctx, lambda)
} else {
BSDF::empty()
}
} }
fn get_bssrdf<T>( fn get_bssrdf<T>(

View file

@ -1,4 +1,3 @@
use bumpalo::Bump;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use std::sync::Arc; use std::sync::Arc;
@ -8,13 +7,14 @@ use crate::core::geometry::{
use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::core::pbrt::{Float, INV_4_PI, PI};
use crate::spectra::{ use crate::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum,
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::containers::SampledGrid; use crate::utils::containers::SampledGrid;
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::transform::TransformGeneric; use crate::utils::transform::TransformGeneric;
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct PhaseFunctionSample { pub struct PhaseFunctionSample {
pub p: Float, pub p: Float,
@ -35,12 +35,14 @@ pub trait PhaseFunctionTrait {
fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float; fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float;
} }
#[repr(C)]
#[enum_dispatch(PhaseFunctionTrait)] #[enum_dispatch(PhaseFunctionTrait)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum PhaseFunction { pub enum PhaseFunction {
HenyeyGreenstein(HGPhaseFunction), HenyeyGreenstein(HGPhaseFunction),
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct HGPhaseFunction { pub struct HGPhaseFunction {
g: Float, g: Float,
@ -85,14 +87,19 @@ impl PhaseFunctionTrait for HGPhaseFunction {
} }
} }
#[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MajorantGrid { pub struct MajorantGrid {
pub bounds: Bounds3f, pub bounds: Bounds3f,
pub res: Point3i, pub res: Point3i,
pub voxels: Vec<Float>, pub voxels: *const Float,
} }
unsafe impl Send for MajorantGrid {}
unsafe impl Sync for MajorantGrid {}
impl MajorantGrid { impl MajorantGrid {
#[cfg(not(target_os = "cuda"))]
pub fn new(bounds: Bounds3f, res: Point3i) -> Self { pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
Self { Self {
bounds, bounds,
@ -100,18 +107,37 @@ impl MajorantGrid {
voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize), voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize),
} }
} }
#[inline(always)]
fn is_valid(&self) -> bool {
!self.voxels.is_null()
}
#[inline(always)]
pub fn lookup(&self, x: i32, y: i32, z: i32) -> Float { pub fn lookup(&self, x: i32, y: i32, z: i32) -> Float {
if !self.is_valid() {
return 0.0;
}
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x; let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
if idx >= 0 && (idx as usize) < self.voxels.len() { if idx >= 0 && (idx as usize) < self.voxels.len() {
self.voxels[idx as usize] unsafe { *self.voxels.add(idx as usize) }
} else { } else {
0.0 0.0
} }
} }
pub fn set(&mut self, x: i32, y: i32, z: i32, v: Float) {
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x; #[inline(always)]
if idx >= 0 && (idx as usize) < self.voxels.len() { pub fn set(&self, x: i32, y: i32, z: i32, v: Float) {
self.voxels[idx as usize] = v; if !self.is_valid() {
return;
}
let idx = x + self.res.x() * (y + self.res.y() * z);
unsafe {
*self.voxels.add(idx as usize) = v;
} }
} }
@ -130,6 +156,7 @@ impl MajorantGrid {
} }
} }
#[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct RayMajorantSegment { pub struct RayMajorantSegment {
t_min: Float, t_min: Float,
@ -137,6 +164,8 @@ pub struct RayMajorantSegment {
sigma_maj: SampledSpectrum, sigma_maj: SampledSpectrum,
} }
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum RayMajorantIterator { pub enum RayMajorantIterator {
Homogeneous(HomogeneousMajorantIterator), Homogeneous(HomogeneousMajorantIterator),
DDA(DDAMajorantIterator), DDA(DDAMajorantIterator),
@ -155,6 +184,9 @@ impl Iterator for RayMajorantIterator {
} }
} }
} }
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct HomogeneousMajorantIterator { pub struct HomogeneousMajorantIterator {
called: bool, called: bool,
seg: RayMajorantSegment, seg: RayMajorantSegment,
@ -186,6 +218,8 @@ impl Iterator for HomogeneousMajorantIterator {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DDAMajorantIterator { pub struct DDAMajorantIterator {
sigma_t: SampledSpectrum, sigma_t: SampledSpectrum,
t_min: Float, t_min: Float,
@ -197,6 +231,7 @@ pub struct DDAMajorantIterator {
voxel_limit: [i32; 3], voxel_limit: [i32; 3],
voxel: [i32; 3], voxel: [i32; 3],
} }
impl DDAMajorantIterator { impl DDAMajorantIterator {
pub fn new( pub fn new(
ray: &Ray, ray: &Ray,
@ -209,7 +244,7 @@ impl DDAMajorantIterator {
t_min, t_min,
t_max, t_max,
sigma_t: *sigma_t, sigma_t: *sigma_t,
grid: grid.clone(), grid: *grid,
next_crossing_t: [0.0; 3], next_crossing_t: [0.0; 3],
delta_t: [0.0; 3], delta_t: [0.0; 3],
step: [0; 3], step: [0; 3],
@ -227,28 +262,29 @@ impl DDAMajorantIterator {
let p_grid_start = grid.bounds.offset(&ray.at(t_min)); let p_grid_start = grid.bounds.offset(&ray.at(t_min));
let grid_intersect = Vector3f::from(p_grid_start); let grid_intersect = Vector3f::from(p_grid_start);
let res = [grid.res.x, grid.res.y, grid.res.z];
for axis in 0..3 { for axis in 0..3 {
iter.voxel[axis] = clamp( iter.voxel[axis] = clamp(
(grid_intersect[axis] * grid.res[axis] as Float) as i32, (grid_intersect[axis] * res[axis] as Float) as i32,
0, 0,
grid.res[axis] - 1, res[axis] - 1,
); );
iter.delta_t[axis] = 1.0 / (ray_grid_d[axis].abs() * grid.res[axis] as Float); iter.delta_t[axis] = 1.0 / (ray_grid_d[axis].abs() * res[axis] as Float);
if ray_grid_d[axis] == -0.0 { if ray_grid_d[axis] == -0.0 {
ray_grid_d[axis] = 0.0; ray_grid_d[axis] = 0.0;
} }
if ray_grid_d[axis] >= 0.0 { if ray_grid_d[axis] >= 0.0 {
let next_voxel_pos = (iter.voxel[axis] + 1) as Float / grid.res[axis] as Float; let next_voxel_pos = (iter.voxel[axis] + 1) as Float / res[axis] as Float;
iter.next_crossing_t[axis] = iter.next_crossing_t[axis] =
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis]; t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
iter.step[axis] = 1; iter.step[axis] = 1;
iter.voxel_limit[axis] = grid.res[axis]; iter.voxel_limit[axis] = res[axis];
} else { } else {
let next_voxel_pos = (iter.voxel[axis]) as Float / grid.res[axis] as Float; let next_voxel_pos = (iter.voxel[axis]) as Float / res[axis] as Float;
iter.next_crossing_t[axis] = iter.next_crossing_t[axis] =
t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis]; t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis];
iter.step[axis] = -1; iter.step[axis] = -1;
@ -313,6 +349,8 @@ impl Iterator for DDAMajorantIterator {
} }
} }
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MediumProperties { pub struct MediumProperties {
pub sigma_a: SampledSpectrum, pub sigma_a: SampledSpectrum,
pub sigma_s: SampledSpectrum, pub sigma_s: SampledSpectrum,
@ -335,7 +373,6 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
ray: &Ray, ray: &Ray,
t_max: Float, t_max: Float,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
buf: &Bump,
) -> RayMajorantIterator; ) -> RayMajorantIterator;
fn sample_t_maj<F>( fn sample_t_maj<F>(
@ -354,8 +391,7 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug {
t_max *= len; t_max *= len;
ray.d /= len; ray.d /= len;
let buf = Bump::new(); let mut iter = self.sample_ray(&ray, t_max, lambda);
let mut iter = self.sample_ray(&ray, t_max, lambda, &buf);
let mut t_maj = SampledSpectrum::new(1.0); let mut t_maj = SampledSpectrum::new(1.0);
while let Some(seg) = iter.next() { while let Some(seg) = iter.next() {
@ -463,7 +499,6 @@ impl MediumTrait for HomogeneousMedium {
_ray: &Ray, _ray: &Ray,
t_max: Float, t_max: Float,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
_scratch: &Bump,
) -> RayMajorantIterator { ) -> RayMajorantIterator {
let sigma_a = self.sigma_a_spec.sample(lambda); let sigma_a = self.sigma_a_spec.sample(lambda);
let sigma_s = self.sigma_s_spec.sample(lambda); let sigma_s = self.sigma_s_spec.sample(lambda);
@ -475,7 +510,8 @@ impl MediumTrait for HomogeneousMedium {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct GridMedium { pub struct GridMedium {
bounds: Bounds3f, bounds: Bounds3f,
render_from_medium: TransformGeneric<Float>, render_from_medium: TransformGeneric<Float>,
@ -483,7 +519,7 @@ pub struct GridMedium {
sigma_s_spec: DenselySampledSpectrum, sigma_s_spec: DenselySampledSpectrum,
density_grid: SampledGrid<Float>, density_grid: SampledGrid<Float>,
phase: HGPhaseFunction, phase: HGPhaseFunction,
temperature_grid: Option<SampledGrid<Float>>, temperature_grid: SampledGrid<Float>,
le_spec: DenselySampledSpectrum, le_spec: DenselySampledSpectrum,
le_scale: SampledGrid<Float>, le_scale: SampledGrid<Float>,
is_emissive: bool, is_emissive: bool,
@ -492,15 +528,16 @@ pub struct GridMedium {
impl GridMedium { impl GridMedium {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
bounds: &Bounds3f, bounds: &Bounds3f,
render_from_medium: &TransformGeneric<Float>, render_from_medium: &Transform,
sigma_a: &Spectrum, sigma_a: &Spectrum,
sigma_s: &Spectrum, sigma_s: &Spectrum,
sigma_scale: Float, sigma_scale: Float,
g: Float, g: Float,
density_grid: SampledGrid<Float>, density_grid: SampledGrid<Float>,
temperature_grid: Option<SampledGrid<Float>>, temperature_grid: SampledGrid<Float>,
le: &Spectrum, le: &Spectrum,
le_scale: SampledGrid<Float>, le_scale: SampledGrid<Float>,
) -> Self { ) -> Self {
@ -588,7 +625,6 @@ impl MediumTrait for GridMedium {
ray: &Ray, ray: &Ray,
t_max: Float, t_max: Float,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
_buf: &Bump,
) -> RayMajorantIterator { ) -> RayMajorantIterator {
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max)); let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
@ -621,29 +657,31 @@ impl MediumTrait for GridMedium {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct RGBGridMedium { pub struct RGBGridMedium {
bounds: Bounds3f, bounds: Bounds3f,
render_from_medium: TransformGeneric<Float>, render_from_medium: Transform,
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>,
le_scale: Float,
phase: HGPhaseFunction, phase: HGPhaseFunction,
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>, le_scale: Float,
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>,
sigma_scale: Float, sigma_scale: Float,
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
le_grid: SampledGrid<RGBIlluminantSpectrum>,
majorant_grid: MajorantGrid, majorant_grid: MajorantGrid,
} }
impl RGBGridMedium { impl RGBGridMedium {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[cfg(not(target_os = "cuda"))]
pub fn new( pub fn new(
bounds: &Bounds3f, bounds: &Bounds3f,
render_from_medium: &TransformGeneric<Float>, render_from_medium: &TransformGeneric<Float>,
g: Float, g: Float,
sigma_a_grid: Option<SampledGrid<RGBUnboundedSpectrum>>, sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: Option<SampledGrid<RGBUnboundedSpectrum>>, sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_scale: Float, sigma_scale: Float,
le_grid: Option<SampledGrid<RGBIlluminantSpectrum>>, le_grid: SampledGrid<RGBIlluminantSpectrum>,
le_scale: Float, le_scale: Float,
) -> Self { ) -> Self {
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16)); let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
@ -679,31 +717,29 @@ impl RGBGridMedium {
impl MediumTrait for RGBGridMedium { impl MediumTrait for RGBGridMedium {
fn is_emissive(&self) -> bool { fn is_emissive(&self) -> bool {
self.le_grid.is_some() && self.le_scale > 0. self.le_grid.is_valid() && self.le_scale > 0.
} }
fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties { fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties {
let p_transform = self.render_from_medium.apply_inverse(p); let p_transform = self.render_from_medium.apply_inverse(p);
let p = Point3f::from(self.bounds.offset(&p_transform)); let p = Point3f::from(self.bounds.offset(&p_transform));
let convert = |s: &RGBUnboundedSpectrum| s.sample(lambda); let convert = |s: &RGBUnboundedSpectrum| s.sample(lambda);
let sigma_a = self.sigma_scale let sigma_a = if self.sigma_a_grid.is_valid() {
* self self.sigma_scale * self.sigma_a_grid.lookup_convert(p, convert)
.sigma_a_grid } else {
.as_ref() SampledSpectrum::new(1.0) * self.sigma_scale // Or 0.0? Check PBRT logic
.map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert)); };
let sigma_s = if self.sigma_s_grid.is_valid() {
self.sigma_scale * self.sigma_s_grid.lookup_convert(p, convert)
} else {
SampledSpectrum::new(1.0) * self.sigma_scale
};
let sigma_s = self.sigma_scale let le = if self.le_grid.is_valid() && self.le_scale > 0.0 {
* self self.le_grid.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale
.sigma_s_grid } else {
.as_ref() SampledSpectrum::new(0.0)
.map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert)); };
let le = self
.le_grid
.as_ref()
.filter(|_| self.le_scale > 0.0)
.map(|g| g.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale)
.unwrap_or_else(|| SampledSpectrum::new(0.0));
MediumProperties { MediumProperties {
sigma_a, sigma_a,
@ -718,7 +754,6 @@ impl MediumTrait for RGBGridMedium {
ray: &Ray, ray: &Ray,
t_max: Float, t_max: Float,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
_buf: &Bump,
) -> RayMajorantIterator { ) -> RayMajorantIterator {
let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max)); let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max));
@ -732,6 +767,7 @@ impl MediumTrait for RGBGridMedium {
if local_ray.d.y() < 0.0 { 1 } else { 0 }, if local_ray.d.y() < 0.0 { 1 } else { 0 },
if local_ray.d.z() < 0.0 { 1 } else { 0 }, if local_ray.d.z() < 0.0 { 1 } else { 0 },
]; ];
let (t_min, t_max) = let (t_min, t_max) =
match self match self
.bounds .bounds
@ -763,7 +799,6 @@ impl MediumTrait for CloudMedium {
_ray: &Ray, _ray: &Ray,
_t_max: Float, _t_max: Float,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
_buf: &Bump,
) -> RayMajorantIterator { ) -> RayMajorantIterator {
todo!() todo!()
} }
@ -782,28 +817,43 @@ impl MediumTrait for NanoVDBMedium {
_ray: &Ray, _ray: &Ray,
_t_max: Float, _t_max: Float,
_lambda: &SampledWavelengths, _lambda: &SampledWavelengths,
_buf: &Bump,
) -> RayMajorantIterator { ) -> RayMajorantIterator {
todo!() todo!()
} }
} }
#[derive(Debug, Default, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MediumInterface { pub struct MediumInterface {
pub inside: Option<Arc<Medium>>, pub inside: *const Medium,
pub outside: Option<Arc<Medium>>, pub outside: *const Medium,
} }
impl MediumInterface { unsafe impl Send for MediumInterface {}
pub fn new(inside: Option<Arc<Medium>>, outside: Option<Arc<Medium>>) -> Self { unsafe impl Sync for MediumInterface {}
Self { inside, outside }
}
pub fn is_medium_transition(&self) -> bool { impl Default for MediumInterface {
match (&self.inside, &self.outside) { fn default() -> Self {
(Some(inside), Some(outside)) => !Arc::ptr_eq(inside, outside), Self {
(None, None) => false, inside: core::ptr::null(),
_ => true, outside: core::ptr::null(),
} }
} }
} }
impl MediumInterface {
pub fn new(inside: *const Medium, outside: *const Medium) -> Self {
Self { inside, outside }
}
pub fn empty() -> Self {
Self {
inside: core::ptr::null(),
outside: core::ptr::null(),
}
}
pub fn is_medium_transition(&self) -> bool {
self.inside != self.outside
}
}

View file

@ -2,6 +2,7 @@ pub mod aggregates;
pub mod bssrdf; pub mod bssrdf;
pub mod bxdf; pub mod bxdf;
pub mod camera; pub mod camera;
pub mod color;
pub mod film; pub mod film;
pub mod filter; pub mod filter;
pub mod geometry; pub mod geometry;

View file

@ -1,11 +1,11 @@
use crate::core::aggregates::LinearBVHNode; use crate::core::aggregates::LinearBVHNode;
use crate::core::geometry::{Bounds3f, Ray}; use crate::core::geometry::{Bounds3f, Ray};
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::light::Light;
use crate::core::material::Material; use crate::core::material::Material;
use crate::core::medium::{Medium, MediumInterface}; use crate::core::medium::{Medium, MediumInterface};
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::texture::{FloatTextureTrait, TextureEvalContext}; use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
use crate::lights::Light;
use crate::shapes::{Shape, ShapeIntersection, ShapeTrait}; use crate::shapes::{Shape, ShapeIntersection, ShapeTrait};
use crate::utils::hash::hash_float; use crate::utils::hash::hash_float;
use crate::utils::transform::{AnimatedTransform, TransformGeneric}; use crate::utils::transform::{AnimatedTransform, TransformGeneric};
@ -14,21 +14,25 @@ use enum_dispatch::enum_dispatch;
use std::sync::Arc; use std::sync::Arc;
#[enum_dispatch] #[enum_dispatch]
pub trait PrimitiveTrait: Send + Sync + std::fmt::Debug { pub trait PrimitiveTrait {
fn bounds(&self) -> Bounds3f; fn bounds(&self) -> Bounds3f;
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>; fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool; fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool;
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct GeometricPrimitive { pub struct GeometricPrimitive {
shape: Arc<Shape>, shape: *const Shape,
material: Arc<Material>, material: *const Material,
area_light: Arc<Light>, area_light: *const Light,
medium_interface: MediumInterface, medium_interface: MediumInterface,
alpha: Option<Arc<dyn FloatTextureTrait>>, alpha: *const GPUFloatTexture,
} }
unsafe impl Send for GeometricPrimitive {}
unsafe impl Sync for GeometricPrimitive {}
impl PrimitiveTrait for GeometricPrimitive { impl PrimitiveTrait for GeometricPrimitive {
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
self.shape.bounds() self.shape.bounds()
@ -80,8 +84,9 @@ impl PrimitiveTrait for GeometricPrimitive {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
pub struct SimplePrimitiv { #[derive(Debug, Copy, Clone)]
pub struct SimplePrimitive {
shape: Arc<Shape>, shape: Arc<Shape>,
material: Arc<Material>, material: Arc<Material>,
} }

View file

@ -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(_))
}
}

View file

@ -1,20 +1,21 @@
use crate::core::color::ColorEncoding;
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta,
}; };
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::Float;
use crate::core::pbrt::{INV_2_PI, INV_PI, PI};
use crate::images::WrapMode; use crate::images::WrapMode;
use crate::spectra::color::ColorEncoding;
use crate::spectra::{ use crate::spectra::{
RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, Spectrum, SpectrumTrait, SampledWavelengths,
}; };
use crate::textures::*; use crate::textures::*;
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::square; use crate::utils::math::square;
use crate::{Float, INV_2_PI, INV_PI, PI};
use enum_dispatch::enum_dispatch;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct TexCoord2D { pub struct TexCoord2D {
pub st: Point2f, pub st: Point2f,
pub dsdx: Float, pub dsdx: Float,
@ -24,7 +25,7 @@ pub struct TexCoord2D {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Copy)]
pub enum TextureMapping2D { pub enum TextureMapping2D {
UV(UVMapping), UV(UVMapping),
Spherical(SphericalMapping), Spherical(SphericalMapping),
@ -43,12 +44,13 @@ impl TextureMapping2D {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct UVMapping { pub struct UVMapping {
su: Float, pub su: Float,
sv: Float, pub sv: Float,
du: Float, pub du: Float,
dv: Float, pub dv: Float,
} }
impl Default for UVMapping { impl Default for UVMapping {
@ -83,7 +85,8 @@ impl UVMapping {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct SphericalMapping { pub struct SphericalMapping {
texture_from_render: Transform, texture_from_render: Transform,
} }
@ -124,7 +127,8 @@ impl SphericalMapping {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct CylindricalMapping { pub struct CylindricalMapping {
texture_from_render: Transform, texture_from_render: Transform,
} }
@ -158,7 +162,8 @@ impl CylindricalMapping {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct PlanarMapping { pub struct PlanarMapping {
texture_from_render: Transform, texture_from_render: Transform,
vs: Vector3f, vs: Vector3f,
@ -203,6 +208,8 @@ impl PlanarMapping {
} }
} }
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct TexCoord3D { pub struct TexCoord3D {
pub p: Point3f, pub p: Point3f,
pub dpdx: Vector3f, pub dpdx: Vector3f,
@ -213,7 +220,8 @@ pub trait TextureMapping3DTrait {
fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D; fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D;
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub enum TextureMapping3D { pub enum TextureMapping3D {
PointTransform(PointTransformMapping), PointTransform(PointTransformMapping),
} }
@ -226,15 +234,17 @@ impl TextureMapping3D {
} }
} }
#[derive(Clone, Debug)] #[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct PointTransformMapping { pub struct PointTransformMapping {
texture_from_render: Transform, pub texture_from_render: Transform,
} }
impl PointTransformMapping { impl PointTransformMapping {
pub fn new(texture_from_render: &Transform) -> Self { #[cfg(not(target_os = "cuda"))]
pub fn new(texture_from_render: Transform) -> Self {
Self { Self {
texture_from_render: *texture_from_render, texture_from_render,
} }
} }
@ -247,7 +257,8 @@ impl PointTransformMapping {
} }
} }
#[derive(Clone, Default, Debug)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct TextureEvalContext { pub struct TextureEvalContext {
pub p: Point3f, pub p: Point3f,
pub dpdx: Vector3f, pub dpdx: Vector3f,
@ -325,10 +336,11 @@ impl From<&Interaction> for TextureEvalContext {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)]
pub enum GPUFloatTexture { pub enum GPUFloatTexture {
Constant(FloatConstantTexture), Constant(FloatConstantTexture),
DirectionMix(FloatDirectionMixTexture), DirectionMix(GPUFloatDirectionMixTexture),
Scaled(FloatScaledTexture), Scaled(GPUFloatScaledTexture),
Bilerp(FloatBilerpTexture), Bilerp(FloatBilerpTexture),
Checkerboard(FloatCheckerboardTexture), Checkerboard(FloatCheckerboardTexture),
Dots(FloatDotsTexture), Dots(FloatDotsTexture),
@ -359,6 +371,7 @@ impl GPUFloatTexture {
} }
} }
#[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum SpectrumType { pub enum SpectrumType {
Illuminant, Illuminant,
@ -367,14 +380,16 @@ pub enum SpectrumType {
} }
#[repr(C)] #[repr(C)]
#[enum_dispatch]
#[derive(Clone, Copy, Debug)]
pub enum GPUSpectrumTexture { pub enum GPUSpectrumTexture {
Constant(SpectrumConstantTexture), Constant(SpectrumConstantTexture),
Bilerp(SpectrumBilerpTexture), Bilerp(SpectrumBilerpTexture),
Checkerboard(SpectrumCheckerboardTexture), Checkerboard(SpectrumCheckerboardTexture),
Marble(MarbleTexture), Marble(MarbleTexture),
DirectionMix(SpectrumDirectionMixTexture), DirectionMix(GPUSpectrumDirectionMixTexture),
Dots(SpectrumDotsTexture), Dots(SpectrumDotsTexture),
Scaled(SpectrumScaledTexture), Scaled(GPUSpectrumScaledTexture),
Image(GPUSpectrumImageTexture), Image(GPUSpectrumImageTexture),
Ptex(GPUSpectrumPtexTexture), Ptex(GPUSpectrumPtexTexture),
Mix(GPUSpectrumMixTexture), Mix(GPUSpectrumMixTexture),
@ -396,3 +411,42 @@ impl GPUSpectrumTexture {
} }
} }
} }
pub trait TextureEvaluator: Send + Sync {
fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float;
fn evaluate_spectrum(
&self,
tex: &GPUSpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum;
fn can_evaluate(&self, _ftex: &[&GPUFloatTexture], _stex: &[&GPUSpectrumTexture]) -> bool;
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct UniversalTextureEvaluator;
impl TextureEvaluator for UniversalTextureEvaluator {
fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float {
tex.evaluate(ctx)
}
fn evaluate_spectrum(
&self,
tex: &GPUSpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
tex.evaluate(ctx, lambda)
}
fn can_evaluate(
&self,
_float_textures: &[&GPUFloatTexture],
_spectrum_textures: &[&GPUSpectrumTexture],
) -> bool {
true
}
}

View file

@ -3,3 +3,9 @@ pub mod gaussian;
pub mod lanczos; pub mod lanczos;
pub mod mitchell; pub mod mitchell;
pub mod triangle; pub mod triangle;
pub use boxf::*;
pub use gaussian::*;
pub use lanczos::*;
pub use mitchell::*;
pub use triangle::*;

View file

@ -2,10 +2,10 @@ mod pipeline;
use pipeline::*; use pipeline::*;
use crate::camera::{Camera, CameraTrait};
use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction}; use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction};
use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode};
use crate::core::film::{FilmTrait, VisibleSurface}; use crate::core::camera::Camera;
use crate::core::film::VisibleSurface;
use crate::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike}; use crate::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike};
use crate::core::interaction::{ use crate::core::interaction::{
self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction, self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
@ -15,8 +15,8 @@ use crate::core::options::get_options;
use crate::core::pbrt::{Float, SHADOW_EPSILON}; use crate::core::pbrt::{Float, SHADOW_EPSILON};
use crate::core::primitive::{Primitive, PrimitiveTrait}; use crate::core::primitive::{Primitive, PrimitiveTrait};
use crate::core::sampler::{CameraSample, Sampler, SamplerTrait}; use crate::core::sampler::{CameraSample, Sampler, SamplerTrait};
use crate::lights::sampler::LightSamplerTrait;
use crate::lights::sampler::{LightSampler, UniformLightSampler}; use crate::lights::sampler::{LightSampler, UniformLightSampler};
use crate::lights::{Light, LightSampleContext, LightTrait, sampler::LightSamplerTrait};
use crate::shapes::ShapeIntersection; use crate::shapes::ShapeIntersection;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::hash::{hash_buffer, mix_bits}; use crate::utils::hash::{hash_buffer, mix_bits};

View file

@ -1,26 +1,259 @@
use crate::PI;
use crate::core::color::{RGB, XYZ};
use crate::core::geometry::*; use crate::core::geometry::*;
use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction};
use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use crate::core::medium::MediumInterface;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
// use crate::core::texture::GpuFloatTextureHandle; use crate::core::spectrum::{Spectrum, SpectrumTrait};
// use crate::shapes::GpuShapeHandle; use crate::core::texture::{GPUFloatTexture, TextureEvalContext, UniversalTextureEvaluator};
use crate::images::Image;
use crate::shapes::{Shape, ShapeSampleContext};
use crate::spectra::*; use crate::spectra::*;
use crate::utils::Transform;
use crate::utils::hash::hash_float;
#[repr(C)] #[derive(Clone, Debug)]
#[derive(Clone, Copy, Default)]
pub struct DiffuseAreaLight { pub struct DiffuseAreaLight {
pub lemit_coeffs: [Float; 32], pub base: LightBase,
pub scale: Float, pub shape: *const Shape,
pub alpha: *const GPUFloatTexture,
pub area: Float, pub area: Float,
pub two_sided: bool, pub two_sided: bool,
pub image_id: i32, pub lemit: DenselySampledSpectrum,
pub shape_id: u32, pub scale: Float,
pub image: *const Image,
pub image_color_space: RGBColorSpace,
} }
impl LightTrait for DiffuseAreaLight { #[cfg(not(target_os = "cuda"))]
fn l(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { impl DiffuseAreaLight {
#[allow(clippy::too_many_arguments)]
pub fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
le: Spectrum,
scale: Float,
shape: Shape,
alpha: *const GPUFloatTexture,
image: *const Image,
image_color_space: *const RGBColorSpace,
two_sided: bool,
) -> Self {
let is_constant_zero = match &alpha {
GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
_ => false,
};
let (light_type, stored_alpha) = if is_constant_zero {
(LightType::DeltaPosition, None)
} else {
(LightType::Area, Some(alpha))
};
let base = LightBase::new(light_type, &render_from_light, &medium_interface);
let lemit = LightBase::lookup_spectrum(&le);
if let Some(im) = &image {
let desc = im
.get_channel_desc(&["R", "G", "B"])
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
assert_eq!(3, desc.size(), "Image channel description size mismatch");
assert!(
desc.is_identity(),
"Image channel description is not identity"
);
assert!(
image_color_space.is_some(),
"Image provided but ColorSpace is missing"
);
}
let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
println!(
"Scaling detected in rendering to light space transformation! \
The system has numerous assumptions, implicit and explicit, \
that this transform will have no scale factors in it. \
Proceed at your own risk; your image may have errors."
);
}
Self {
base,
area: shape.area(),
shape,
alpha: stored_alpha,
two_sided,
lemit,
scale,
image,
image_color_space,
}
}
fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum {
if !self.two_sided && n.dot(wo) <= 0.0 { if !self.two_sided && n.dot(wo) <= 0.0 {
return SampledSpectrum::new(0.0); return SampledSpectrum::new(0.0);
} }
let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs); let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs);
spec.sample(lambda) * self.scale spec.sample(lambda) * self.scale
} }
fn alpha_masked(&self, intr: &Interaction) -> bool {
let Some(alpha_tex) = &self.alpha else {
return false;
};
let ctx = TextureEvalContext::from(intr);
let a = UniversalTextureEvaluator.evaluate_float(alpha_tex, &ctx);
if a >= 1.0 {
return false;
}
if a <= 0.0 {
return true;
}
hash_float(&intr.p()) > a
}
}
impl LightTrait for DiffuseAreaLight {
fn base(&self) -> &LightBase {
&self.base
}
fn sample_li(
&self,
ctx: &LightSampleContext,
u: Point2f,
lambda: &SampledWavelengths,
_allow_incomplete_pdf: bool,
) -> Option<LightLiSample> {
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.0);
let ss = self.shape.sample_from_context(&shape_ctx, u)?;
let mut intr: SurfaceInteraction = ss.intr.as_ref().clone();
intr.common.medium_interface = Some(self.base.medium_interface.clone());
let p = intr.p();
let n = intr.n();
let uv = intr.uv;
let generic_intr = Interaction::Surface(intr);
if self.alpha_masked(&generic_intr) {
return None;
}
let wi = (p - ctx.p()).normalize();
let le = self.l(p, n, uv, -wi, lambda);
if le.is_black() {
return None;
}
Some(LightLiSample::new(le, wi, ss.pdf, generic_intr))
}
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
let shape_ctx = ShapeSampleContext::new(ctx.pi, ctx.n, ctx.ns, 0.);
self.shape.pdf_from_context(&shape_ctx, wi)
}
fn l(
&self,
p: Point3f,
n: Normal3f,
mut uv: Point2f,
w: Vector3f,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
if self.two_sided && n.dot(w.into()) < 0. {
return SampledSpectrum::new(0.);
}
let intr = Interaction::Surface(SurfaceInteraction::new_minimal(
Point3fi::new_from_point(p),
uv,
));
if self.alpha_masked(&intr) {
return SampledSpectrum::new(0.);
}
if let Some(image) = &self.image {
let mut rgb = RGB::default();
uv[1] = 1. - uv[1];
for c in 0..3 {
rgb[c] = image.bilerp_channel(uv, c);
}
let spec = RGBIlluminantSpectrum::new(
self.image_color_space.as_ref().unwrap(),
RGB::clamp_zero(rgb),
);
self.scale * spec.sample(lambda)
} else {
self.scale * self.lemit.sample(lambda)
}
}
fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut l = SampledSpectrum::new(0.);
if let Some(image) = &self.image {
for y in 0..image.resolution().y() {
for x in 0..image.resolution().x() {
let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = image.get_channel(Point2i::new(x, y), c);
}
l += RGBIlluminantSpectrum::new(
self.image_color_space.as_ref().unwrap(),
RGB::clamp_zero(rgb),
)
.sample(&lambda);
}
}
l *= self.scale / (image.resolution().x() * image.resolution().y()) as Float;
} else {
l = self.lemit.sample(&lambda) * self.scale;
}
let two_side = if self.two_sided { 2. } else { 1. };
PI * two_side * self.area * l
}
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
unimplemented!()
}
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> {
let mut phi = 0.;
if let Some(image) = &self.image {
for y in 0..image.resolution.y() {
for x in 0..image.resolution.x() {
for c in 0..3 {
phi += image.get_channel(Point2i::new(x, y), c);
}
}
}
} else {
phi = self.lemit.max_value();
}
let nb = self.shape.normal_bounds();
Some(LightBounds::new(
&self.shape.bounds(),
nb.w,
phi,
nb.cos_theta,
(PI / 2.).cos(),
self.two_sided,
))
}
} }

View 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
}
}

View 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
}
}

View file

@ -11,30 +11,19 @@ use crate::{
}, },
}; };
use rayon::prelude::*; use crate::images::{PixelFormat, WrapMode};
use crate::image::{PixelFormat, WrapMode};
use std::path::Path;
use super::{
Arc, Bounds2f, Bounds3f, DenselySampledSpectrum, Float, Image, Interaction, LightBaseData,
LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, MediumInterface,
Normal3f, PI, Point2f, Point2i, Point3f, Ray, SampledSpectrum, SampledWavelengths,
SimpleInteraction, Spectrum, TransformGeneric, Vector3f, VectorLike,
equal_area_sphere_to_square, square,
};
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Copy, Clone)]
pub struct InfiniteUniformLight { pub struct InfiniteUniformLight {
base: LightBaseData, pub base: LightBase,
lemit: u32, pub lemit: u32,
scale: Float, pub scale: Float,
scene_center: Point3f, pub scene_center: Point3f,
scene_radius: Float, pub scene_radius: Float,
} }
#[cfg(not(target_os = "cuda"))]
impl InfiniteUniformLight { impl InfiniteUniformLight {
pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self { pub fn new(render_from_light: TransformGeneric<Float>, le: Spectrum, scale: Float) -> Self {
let base = LightBase::new( let base = LightBase::new(
@ -83,10 +72,6 @@ impl LightTrait for InfiniteUniformLight {
)) ))
} }
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
}
fn pdf_li( fn pdf_li(
&self, &self,
_ctx: &LightSampleContext, _ctx: &LightSampleContext,
@ -119,6 +104,9 @@ impl LightTrait for InfiniteUniformLight {
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
todo!() todo!()
} }
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -133,6 +121,7 @@ pub struct InfiniteImageLight {
compensated_distrib: PiecewiseConstant2D, compensated_distrib: PiecewiseConstant2D,
} }
#[cfg(not(target_os = "cuda"))]
impl InfiniteImageLight { impl InfiniteImageLight {
pub fn new( pub fn new(
render_from_light: TransformGeneric<Float>, render_from_light: TransformGeneric<Float>,
@ -213,6 +202,7 @@ impl LightTrait for InfiniteImageLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
fn sample_li( fn sample_li(
&self, &self,
ctx: &LightSampleContext, ctx: &LightSampleContext,
@ -245,30 +235,6 @@ impl LightTrait for InfiniteImageLight {
Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr)) Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr))
} }
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum_l = SampledSpectrum::new(0.);
let width = self.image.resolution.x();
let height = self.image.resolution.y();
for v in 0..height {
for u in 0..width {
let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = self.image.get_channel_with_wrap(
Point2i::new(u, v),
c,
WrapMode::OctahedralSphere.into(),
);
}
sum_l += RGBIlluminantSpectrum::new(
self.image_color_space.as_ref(),
RGB::clamp_zero(rgb),
)
.sample(&lambda);
}
}
4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float
}
fn pdf_li(&self, _ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float { fn pdf_li(&self, _ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float {
let w_light = self.base.render_from_light.apply_inverse_vector(wi); let w_light = self.base.render_from_light.apply_inverse_vector(wi);
let uv = equal_area_sphere_to_square(w_light); let uv = equal_area_sphere_to_square(w_light);
@ -301,22 +267,50 @@ impl LightTrait for InfiniteImageLight {
self.image_le(uv, lambda) self.image_le(uv, lambda)
} }
#[cfg(not(target_os = "cuda"))]
fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum {
let mut sum_l = SampledSpectrum::new(0.);
let width = self.image.resolution.x();
let height = self.image.resolution.y();
for v in 0..height {
for u in 0..width {
let mut rgb = RGB::default();
for c in 0..3 {
rgb[c] = self.image.get_channel_with_wrap(
Point2i::new(u, v),
c,
WrapMode::OctahedralSphere.into(),
);
}
sum_l += RGBIlluminantSpectrum::new(
self.image_color_space.as_ref(),
RGB::clamp_zero(rgb),
)
.sample(&lambda);
}
}
4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float
}
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, scene_bounds: &Bounds3f) { fn preprocess(&mut self, scene_bounds: &Bounds3f) {
let (scene_center, scene_radius) = scene_bounds.bounding_sphere(); let (scene_center, scene_radius) = scene_bounds.bounding_sphere();
self.scene_center = scene_center; self.scene_center = scene_center;
self.scene_radius = scene_radius; self.scene_radius = scene_radius;
} }
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
None None
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct InfinitePortalLight { pub struct InfinitePortalLight {
pub base: LightBaseData, pub base: LightBase,
pub image: Image, pub image: Image,
pub image_color_space: Arc<RGBColorSpace>, pub image_color_space: RGBColorSpace,
pub scale: Float, pub scale: Float,
pub filename: String, pub filename: String,
pub portal: [Point3f; 4], pub portal: [Point3f; 4],
@ -326,10 +320,8 @@ pub struct InfinitePortalLight {
pub scene_radius: Float, pub scene_radius: Float,
} }
#[cfg(not(target_os = "cuda"))]
impl InfinitePortalLight { impl InfinitePortalLight {
fn base(&self) -> &LightBase {
&self.base
}
pub fn new( pub fn new(
render_from_light: TransformGeneric<Float>, render_from_light: TransformGeneric<Float>,
equal_area_image: &Image, equal_area_image: &Image,
@ -520,6 +512,7 @@ impl LightTrait for InfinitePortalLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
} }
fn sample_li( fn sample_li(
&self, &self,
ctx: &LightSampleContext, ctx: &LightSampleContext,
@ -541,10 +534,6 @@ impl LightTrait for InfinitePortalLight {
Some(LightLiSample::new(l, wi, pdf, intr)) Some(LightLiSample::new(l, wi, pdf, intr))
} }
fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum {
todo!()
}
fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float { fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float {
let Some((uv, duv_dw)) = self.image_from_render(wi) else { let Some((uv, duv_dw)) = self.image_from_render(wi) else {
return 0.; return 0.;
@ -576,10 +565,17 @@ impl LightTrait for InfinitePortalLight {
} }
} }
#[cfg(not(target_os = "cuda"))]
fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum {
todo!()
}
#[cfg(not(target_os = "cuda"))]
fn preprocess(&mut self, _scene_bounds: &Bounds3f) { fn preprocess(&mut self, _scene_bounds: &Bounds3f) {
todo!() todo!()
} }
#[cfg(not(target_os = "cuda"))]
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
None None
} }

View file

@ -1,3 +1,15 @@
pub mod diffuse; pub mod diffuse;
pub mod distant;
pub mod goniometric;
pub mod infinite; pub mod infinite;
pub mod point;
pub mod projection;
pub mod sampler; pub mod sampler;
pub mod spot;
pub use diffuse::DiffuseAreaLight;
pub use distant::DistantLight;
pub use goniometric::GoniometricLight;
pub use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
pub use point::PointLight;
pub use projection::ProjectionLight;

103
shared/src/lights/point.rs Normal file
View 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,
))
}
}

View 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!()
}
}

View file

@ -1,15 +1,15 @@
use super::{
Bounds3f, Float, Light, LightBounds, LightSampleContext, LightTrait, Normal3f, PI, Point3f,
SampledSpectrum, SampledWavelengths, Vector3f, VectorLike, safe_sqrt, square,
};
use crate::core::geometry::primitives::OctahedralVector; use crate::core::geometry::primitives::OctahedralVector;
use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike};
use crate::core::geometry::{DirectionCone, Normal}; use crate::core::geometry::{DirectionCone, Normal};
use crate::utils::math::{clamp, lerp, sample_discrete}; use crate::utils::math::{clamp, lerp, sample_discrete};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use crate::core::pbrt::ONE_MINUS_EPSILON; use crate::core::light::{LightBounds, LightSampleContext};
use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{safe_sqrt, square};
use crate::utils::sampling::AliasTable; use crate::utils::sampling::AliasTable;
use crate::{Float, ONE_MINUS_EPSILON, PI};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]

111
shared/src/lights/spot.rs Normal file
View 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,
))
}
}

View file

@ -1,5 +1,7 @@
use crate::Float; use crate::Float;
pub const CIE_Y_INTEGRAL: Float = 106.856895;
pub const CIE_SAMPLES: usize = 471; pub const CIE_SAMPLES: usize = 471;
pub const CIE_X: [Float; CIE_SAMPLES] = [ pub const CIE_X: [Float; CIE_SAMPLES] = [
0.0001299000, 0.0001299000,
@ -1425,6 +1427,112 @@ pub const CIE_Z: [Float; CIE_SAMPLES] = [
0.000000000000, 0.000000000000,
]; ];
const D65_NORM: Float = 10566.864005283874576;
macro_rules! N {
($x:literal) => {
($x as Float) / D65_NORM
};
}
pub const CIE_D65: [Float; 95] = [
N!(46.6383),
N!(49.3637),
N!(52.0891),
N!(51.0323),
N!(49.9755),
N!(52.3118),
N!(54.6482),
N!(68.7015),
N!(82.7549),
N!(87.1204),
N!(91.486),
N!(92.4589),
N!(93.4318),
N!(90.057),
N!(86.6823),
N!(95.7736),
N!(104.865),
N!(110.936),
N!(117.008),
N!(117.41),
N!(117.812),
N!(116.336),
N!(114.861),
N!(115.392),
N!(115.923),
N!(112.367),
N(108.811),
N!(109.082),
N!(109.354),
N!(108.578),
N!(107.802),
N!(106.296),
N!(104.79),
N!(106.239),
N!(107.689),
N!(106.047),
N!(104.405),
N!(104.225),
N!(104.046),
N!(102.023),
N!(100.0),
N!(98.1671),
N!(96.3342),
N!(96.0611),
N!(95.788),
N!(92.2368),
N!(88.6856),
N!(89.3459),
N!(90.0062),
N!(89.8026),
N!(89.5991),
N!(88.6489),
N!(87.6987),
N!(85.4936),
N!(83.2886),
N!(83.4939),
N!(83.6992),
N!(81.863),
N!(80.0268),
N!(80.1207),
N!(80.2146),
N!(81.2462),
N!(82.2778),
N!(80.281),
N!(78.2842),
N!(74.0027),
N!(69.7213),
N!(70.6652),
N!(71.6091),
N!(72.979),
N!(74.349),
N!(67.9765),
N!(61.604),
N!(65.7448),
N!(69.8856),
N!(72.4863),
N!(75.087),
N!(69.3398),
N!(63.5927),
N!(55.0054),
N!(46.4182),
N!(56.6118),
N!(66.8054),
N!(65.0941),
N!(63.3828),
N!(63.8434),
N!(64.304),
N!(61.8779),
N!(59.4519),
N!(55.7054),
N!(51.959),
N!(54.6998),
N!(57.4406),
N!(58.8765),
N!(60.3125),
];
pub const CIE_LAMBDA: [i32; CIE_SAMPLES] = [ pub const CIE_LAMBDA: [i32; CIE_SAMPLES] = [
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378,
379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,

View file

@ -1,8 +1,8 @@
use super::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ}; use crate::core::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ};
use crate::core::geometry::Point2f; use crate::core::geometry::Point2f;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, Spectrum}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
use crate::utils::math::SquareMatrix; use crate::utils::math::SquareMatrix3f;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -10,65 +10,32 @@ use std::cmp::{Eq, PartialEq};
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct StandardColorSpaces {
pub srgb: *const RGBColorSpace,
pub dci_p3: *const RGBColorSpace,
pub rec2020: *const RGBColorSpace,
pub aces2065_1: *const RGBColorSpace,
}
#[repr(C)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RGBColorSpace { pub struct RGBColorSpace {
pub r: Point2f, pub r: Point2f,
pub g: Point2f, pub g: Point2f,
pub b: Point2f, pub b: Point2f,
pub w: Point2f, pub w: Point2f,
pub illuminant: Spectrum, pub illuminant: DenselySampledSpectrum,
pub rgb_to_spectrum_table: Arc<RGBToSpectrumTable>, pub rgb_to_spectrum_table: *const RGBToSpectrumTable,
pub xyz_from_rgb: SquareMatrix<Float, 3>, pub xyz_from_rgb: SquareMatrix3f,
pub rgb_from_xyz: SquareMatrix<Float, 3>, pub rgb_from_xyz: SquareMatrix3f,
} }
unsafe impl Send for RGBColorSpace {}
unsafe impl Sync for RGBColorSpace {}
impl RGBColorSpace { impl RGBColorSpace {
pub fn new(
r: Point2f,
g: Point2f,
b: Point2f,
illuminant: Spectrum,
rgb_to_spectrum_table: RGBToSpectrumTable,
) -> Result<Self, Box<dyn Error>> {
let w_xyz: XYZ = illuminant.to_xyz();
let w = w_xyz.xy();
let r_xyz = XYZ::from_xyy(r, Some(1.0));
let g_xyz = XYZ::from_xyy(g, Some(1.0));
let b_xyz = XYZ::from_xyy(b, Some(1.0));
let rgb_values = [
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
[r_xyz.z(), g_xyz.z(), b_xyz.z()],
];
let rgb = SquareMatrix::new(rgb_values);
let c: RGB = rgb.inverse()? * w_xyz;
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
let rgb_from_xyz = xyz_from_rgb
.inverse()
.expect("XYZ from RGB matrix is singular");
Ok(Self {
r,
g,
b,
w,
illuminant,
rgb_to_spectrum_table: Arc::new(rgb_to_spectrum_table),
xyz_from_rgb,
rgb_from_xyz,
})
}
pub fn get_named(name: &str) -> Result<Arc<RGBColorSpace>, String> {
match name.to_lowercase().as_str() {
"aces2065-1" => Ok(Self::aces2065_1().clone()),
"rec2020" => Ok(Self::rec2020().clone()),
"dci-p3" => Ok(Self::dci_p3().clone()),
"srgb" => Ok(Self::srgb().clone()),
_ => Err(format!("Color space '{}' not found", name)),
}
}
pub fn to_xyz(&self, rgb: RGB) -> XYZ { pub fn to_xyz(&self, rgb: RGB) -> XYZ {
self.xyz_from_rgb * rgb self.xyz_from_rgb * rgb
} }
@ -81,71 +48,13 @@ impl RGBColorSpace {
self.rgb_to_spectrum_table.to_polynomial(rgb) self.rgb_to_spectrum_table.to_polynomial(rgb)
} }
pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix<Float, 3> { pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f {
if self == other { if self == other {
return SquareMatrix::default(); return SquareMatrix3f::default();
} }
self.rgb_from_xyz * other.xyz_from_rgb self.rgb_from_xyz * other.xyz_from_rgb
} }
pub fn srgb() -> &'static Arc<Self> {
static SRGB_SPACE: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
let r = Point2f::new(0.64, 0.33);
let g = Point2f::new(0.30, 0.60);
let b = Point2f::new(0.15, 0.06);
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::srgb();
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&SRGB_SPACE
}
pub fn dci_p3() -> &'static Arc<Self> {
static DCI_P3: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
let r = Point2f::new(0.680, 0.320);
let g = Point2f::new(0.265, 0.690);
let b = Point2f::new(0.150, 0.060);
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::dci_p3();
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&DCI_P3
}
pub fn rec2020() -> &'static Arc<Self> {
static REC2020: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
let r = Point2f::new(0.708, 0.292);
let g = Point2f::new(0.170, 0.797);
let b = Point2f::new(0.131, 0.046);
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::rec2020();
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&REC2020
}
pub fn aces2065_1() -> &'static Arc<Self> {
static ACES: Lazy<Arc<RGBColorSpace>> = Lazy::new(|| {
let r = Point2f::new(0.7347, 0.2653);
let g = Point2f::new(0.0000, 1.0000);
let b = Point2f::new(0.0001, -0.0770);
// ACES uses D60
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::aces2065_1();
Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap())
});
&ACES
}
} }
impl PartialEq for RGBColorSpace { impl PartialEq for RGBColorSpace {
@ -154,6 +63,6 @@ impl PartialEq for RGBColorSpace {
&& self.g == other.g && self.g == other.g
&& self.b == other.b && self.b == other.b
&& self.w == other.w && self.w == other.w
&& Arc::ptr_eq(&self.rgb_to_spectrum_table, &other.rgb_to_spectrum_table) && self.rgb_to_spectrum_table == other.rgb_to_spectrum_table
} }
} }

View file

@ -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
}

View file

@ -1,96 +1,13 @@
pub mod cie; pub mod cie;
pub mod color;
pub mod colorspace; pub mod colorspace;
pub mod data;
pub mod rgb; pub mod rgb;
pub mod sampled; pub mod sampled;
pub mod simple; pub mod simple;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use enum_dispatch::enum_dispatch;
pub use color::{ColorEncoding, RGB, RGBSigmoidPolynomial, XYZ};
pub use colorspace::RGBColorSpace; pub use colorspace::RGBColorSpace;
pub use data::*;
pub use rgb::*; pub use rgb::*;
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN}; pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
pub use simple::*; pub use simple::*;
//
#[enum_dispatch]
pub trait SpectrumTrait: Copy {
fn evaluate(&self, lambda: Float) -> Float;
fn max_value(&self) -> Float;
}
// #[cfg(not(target_arch = "spirv"))]
// impl SpectrumTrait for std::sync::Arc<DenselySampledSpectrum> {
// fn evaluate(&self, lambda: Float) -> Float {
// (**self).evaluate(lambda)
// }
// fn max_value(&self) -> Float {
// (**self).max_value()
// }
// }
//
// #[cfg(target_arch = "spirv")] // or target_os = "cuda"
// impl SpectrumTrait for u32 {
// fn evaluate(&self, lambda: Float) -> Float {
// // Here you would call a global function that accesses
// // a static buffer of spectra data
// crate::gpu::lookup_global_spectrum(*self, lambda)
// }
// fn max_value(&self) -> Float {
// crate::gpu::lookup_global_spectrum_max(*self)
// }
// }
//
#[enum_dispatch(SpectrumTrait)]
#[derive(Debug, Clone)]
pub enum Spectrum {
Constant(ConstantSpectrum),
DenselySampled(DenselySampledSpectrum),
PiecewiseLinear(PiecewiseLinearSpectrum),
Blackbody(BlackbodySpectrum),
RGBAlbedo(RGBAlbedoSpectrum),
RGBIlluminant(RGBIlluminantSpectrum),
RGBUnbounded(RGBUnboundedSpectrum),
}
impl Spectrum {
pub fn std_illuminant_d65() -> Self {
todo!()
}
pub fn to_xyz(&self) -> XYZ {
let x = self.inner_product(data::cie_x());
let y = self.inner_product(data::cie_y());
let z = self.inner_product(data::cie_z());
XYZ::new(x, y, z) / CIE_Y_INTEGRAL
}
fn to_rgb(&self, cs: &RGBColorSpace) -> RGB {
let xyz = self.to_xyz();
cs.to_rgb(xyz)
}
pub fn sample(&self, wavelengths: &SampledWavelengths) -> SampledSpectrum {
SampledSpectrum::from_fn(|i| self.evaluate(wavelengths[i]))
}
pub fn inner_product(&self, other: &Spectrum) -> Float {
let mut integral = 0.0;
// Iterate integer wavelengths.
for lambda in LAMBDA_MIN..=LAMBDA_MAX {
let l = lambda as Float;
integral += self.evaluate(l) * other.evaluate(l);
}
integral
}
pub fn is_constant(&self) -> bool {
matches!(self, Spectrum::Constant(_))
}
}

View file

@ -1,13 +1,16 @@
use super::{ use super::{
DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGBColorSpace,
RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumTrait, XYZ, SampledSpectrum, SampledWavelengths,
}; };
use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ};
use crate::core::spectrum::SpectrumTrait;
use crate::Float; use crate::Float;
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RGBAlbedoSpectrum { pub struct RGBAlbedoSpectrum {
rsp: RGBSigmoidPolynomial, pub rsp: RGBSigmoidPolynomial,
} }
impl RGBAlbedoSpectrum { impl RGBAlbedoSpectrum {
@ -16,14 +19,6 @@ impl RGBAlbedoSpectrum {
rsp: cs.to_rgb_coeffs(rgb), rsp: cs.to_rgb_coeffs(rgb),
} }
} }
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default();
for i in 0..N_SPECTRUM_SAMPLES {
s[i] = self.rsp.evaluate(lambda[i]);
}
s
}
} }
impl SpectrumTrait for RGBAlbedoSpectrum { impl SpectrumTrait for RGBAlbedoSpectrum {
@ -36,10 +31,11 @@ impl SpectrumTrait for RGBAlbedoSpectrum {
} }
} }
#[derive(Debug, Clone, Copy)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)]
pub struct UnboundedRGBSpectrum { pub struct UnboundedRGBSpectrum {
scale: Float, pub scale: Float,
rsp: RGBSigmoidPolynomial, pub rsp: RGBSigmoidPolynomial,
} }
impl UnboundedRGBSpectrum { impl UnboundedRGBSpectrum {
@ -68,38 +64,31 @@ impl SpectrumTrait for UnboundedRGBSpectrum {
} }
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct RGBIlluminantSpectrum { pub struct RGBIlluminantSpectrum {
scale: Float, pub scale: Float,
rsp: RGBSigmoidPolynomial, pub rsp: RGBSigmoidPolynomial,
illuminant: DenselySampledSpectrum, pub illuminant: DenselySampledSpectrum,
} }
// impl RGBIlluminantSpectrum { impl RGBIlluminantSpectrum {
// pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self { pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self {
// let illuminant = &cs.illuminant; let illuminant = &cs.illuminant;
// let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant); let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant);
// let m = rgb.max_component_value(); let m = rgb.max_component_value();
// let scale = 2. * m; let scale = 2. * m;
// let rsp = cs.to_rgb_coeffs(if scale == 1. { let rsp = cs.to_rgb_coeffs(if scale == 1. {
// rgb / scale rgb / scale
// } else { } else {
// RGB::new(0., 0., 0.) RGB::new(0., 0., 0.)
// }); });
// Self { Self {
// scale, scale,
// rsp, rsp,
// illuminant: Some(Arc::new(densely_sampled)), illuminant,
// } }
// } }
// }
// pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
// if self.illuminant.is_none() {
// return SampledSpectrum::new(0.);
// }
// SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
// }
// }
impl SpectrumTrait for RGBIlluminantSpectrum { impl SpectrumTrait for RGBIlluminantSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
@ -111,6 +100,13 @@ impl SpectrumTrait for RGBIlluminantSpectrum {
} }
} }
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
if self.illuminant.is_none() {
return SampledSpectrum::new(0.);
}
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
}
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
match &self.illuminant { match &self.illuminant {
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(), Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(),
@ -126,8 +122,8 @@ pub struct RGBSpectrum {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RGBUnboundedSpectrum { pub struct RGBUnboundedSpectrum {
scale: Float, pub scale: Float,
rsp: RGBSigmoidPolynomial, pub rsp: RGBSigmoidPolynomial,
} }
impl Default for RGBUnboundedSpectrum { impl Default for RGBUnboundedSpectrum {
@ -155,10 +151,6 @@ impl RGBUnboundedSpectrum {
Self { scale, rsp } Self { scale, rsp }
} }
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i]))
}
} }
impl SpectrumTrait for RGBUnboundedSpectrum { impl SpectrumTrait for RGBUnboundedSpectrum {

View file

@ -1,14 +1,13 @@
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
use crate::core::spectrum::StandardSpectra;
use crate::utils::math::{clamp, lerp}; use crate::utils::math::{clamp, lerp};
use std::ops::{ use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use super::{cie_x, cie_y, cie_z};
pub const CIE_Y_INTEGRAL: Float = 106.856895; pub const CIE_Y_INTEGRAL: Float = 106.856895;
pub const N_SPECTRUM_SAMPLES: usize = 1200; pub const N_SPECTRUM_SAMPLES: usize = 4;
pub const LAMBDA_MIN: i32 = 360; pub const LAMBDA_MIN: i32 = 360;
pub const LAMBDA_MAX: i32 = 830; pub const LAMBDA_MAX: i32 = 830;
@ -43,10 +42,13 @@ impl SampledSpectrum {
} }
} }
pub fn from_vector(v: Vec<Float>) -> Self { pub fn from_array(v: &[Float]) -> Self {
assert!(N_SPECTRUM_SAMPLES == v.len());
let mut values = [0.0; N_SPECTRUM_SAMPLES]; let mut values = [0.0; N_SPECTRUM_SAMPLES];
let count = v.len().min(N_SPECTRUM_SAMPLES); for i in 0..N_SPECTRUM_SAMPLES {
values[..count].copy_from_slice(&v[..count]); values[i] = v[i];
}
Self { values } Self { values }
} }
@ -115,8 +117,8 @@ impl SampledSpectrum {
SampledSpectrum::from_fn(|i| if b[i] != 0. { a[i] / b[i] } else { 0. }) SampledSpectrum::from_fn(|i| if b[i] != 0. { a[i] / b[i] } else { 0. })
} }
pub fn y(&self, lambda: &SampledWavelengths) -> Float { pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float {
let ys = cie_y().sample(lambda); let ys = std.cie_y().sample(lambda);
let pdf = lambda.pdf(); let pdf = lambda.pdf();
SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL
} }
@ -294,8 +296,8 @@ impl Neg for SampledSpectrum {
} }
} }
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct SampledWavelengths { pub struct SampledWavelengths {
pub lambda: [Float; N_SPECTRUM_SAMPLES], pub lambda: [Float; N_SPECTRUM_SAMPLES],
pub pdf: [Float; N_SPECTRUM_SAMPLES], pub pdf: [Float; N_SPECTRUM_SAMPLES],

View file

@ -1,18 +1,16 @@
use super::cie::*;
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::core::cie::*; use crate::Float;
use crate::core::pbrt::Float; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{ use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, use core::slice;
};
use crate::utils::file::read_float_file;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::LazyLock; use std::sync::LazyLock;
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct ConstantSpectrum { pub struct ConstantSpectrum {
c: Float, pub c: Float,
} }
impl ConstantSpectrum { impl ConstantSpectrum {
@ -31,90 +29,61 @@ impl SpectrumTrait for ConstantSpectrum {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DenselySampledSpectrum { pub struct DenselySampledSpectrum {
lambda_min: i32, pub lambda_min: i32,
lambda_max: i32, pub lambda_max: i32,
values: Vec<Float>, pub values: *const Float,
} }
unsafe impl Send for DenselySampledSpectrum {}
unsafe impl Sync for DenselySampledSpectrum {}
impl DenselySampledSpectrum { impl DenselySampledSpectrum {
pub fn new(lambda_min: i32, lambda_max: i32) -> Self { #[inline(always)]
let n_values = (lambda_max - lambda_min + 1).max(0) as usize; fn as_slice(&self) -> &[Float] {
Self { if self.values.is_null() {
lambda_min, return &[];
lambda_max,
values: vec![0.0; n_values],
} }
} let len = (self.lambda_max - self.lambda_min + 1).max(0) as usize;
unsafe { slice::from_raw_parts(self.values, len) }
pub fn from_spectrum(spec: &Spectrum) -> Self {
let lambda_min = LAMBDA_MIN;
let lambda_max = LAMBDA_MAX;
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = spec.evaluate(lambda as Float);
}
s
}
pub fn from_spectrum_with_range(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = spec.evaluate(lambda as Float);
}
s
}
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
where
F: Fn(Float) -> Float,
{
let mut s = Self::new(lambda_min, lambda_max);
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
s.values[index] = f(lambda as Float);
}
s
} }
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default(); let mut s = SampledSpectrum::default();
for i in 0..N_SPECTRUM_SAMPLES { for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize; let offset = lambda[i].round() as i32 - self.lambda_min;
if offset >= self.values.len() { let len = (self.lambda_max - self.lambda_min + 1) as i32;
s[i] = 0.;
if offset < 0 || offset >= len {
s[i] = 0.0;
} else { } else {
s[i] = self.values[offset]; unsafe { s[i] = *self.values.add(offset as usize) };
} }
} }
s s
} }
pub fn min_component_value(&self) -> Float { pub fn min_component_value(&self) -> Float {
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b)) self.as_slice()
.iter()
.fold(Float::INFINITY, |a, &b| a.min(b))
} }
pub fn max_component_value(&self) -> Float { pub fn max_component_value(&self) -> Float {
self.values self.as_slice()
.iter() .iter()
.fold(Float::NEG_INFINITY, |a, &b| a.max(b)) .fold(Float::NEG_INFINITY, |a, &b| a.max(b))
} }
pub fn average(&self) -> Float { pub fn average(&self) -> Float {
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float) let slice = self.as_slice();
if slice.is_empty() {
return 0.0;
}
slice.iter().sum::<Float>() / (slice.len() as Float)
} }
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self { pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
@ -128,12 +97,6 @@ impl DenselySampledSpectrum {
} }
r r
} }
pub fn scale(&mut self, factor: Float) {
for v in &mut self.values {
*v *= factor;
}
}
} }
impl PartialEq for DenselySampledSpectrum { impl PartialEq for DenselySampledSpectrum {
@ -180,66 +143,16 @@ impl SpectrumTrait for DenselySampledSpectrum {
} }
} }
#[derive(Debug, Clone)] #[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct PiecewiseLinearSpectrum { pub struct PiecewiseLinearSpectrum {
pub lambdas: Vec<Float>, pub lambdas: *const Float,
pub values: Vec<Float>, pub values: *const Float,
pub count: u32,
} }
impl PiecewiseLinearSpectrum { unsafe impl Send for PiecewiseLinearSpectrum {}
pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self { unsafe impl Sync for PiecewiseLinearSpectrum {}
if data.len() % 2 != 0 {
panic!("Interleaved data must have an even number of elements");
}
let mut temp: Vec<(Float, Float)> =
data.chunks(2).map(|chunk| (chunk[0], chunk[1])).collect();
temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
let (lambdas, values): (Vec<Float>, Vec<Float>) = temp.into_iter().unzip();
// Normalization left to the reader
// This usually involves integrating the curve and scaling 'values'
Self { lambdas, values }
}
pub fn read(filepath: &str) -> Option<Self> {
let vals = match read_float_file(filepath) {
Ok(v) => v,
Err(_) => Vec::new(),
};
if vals.is_empty() {
return None;
}
if vals.len() % 2 == 0 {
return None;
}
let count = vals.len() / 2;
let mut lambdas = Vec::with_capacity(count);
let mut values = Vec::with_capacity(count);
for (_, pair) in vals.chunks(2).enumerate() {
let curr_lambda = pair[0];
let curr_val = pair[1];
if let Some(&prev_lambda) = lambdas.last() {
if curr_lambda <= prev_lambda {
return None;
}
}
lambdas.push(curr_lambda);
values.push(curr_val);
}
Some(PiecewiseLinearSpectrum { lambdas, values })
}
}
impl SpectrumTrait for PiecewiseLinearSpectrum { impl SpectrumTrait for PiecewiseLinearSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
@ -274,10 +187,11 @@ impl SpectrumTrait for PiecewiseLinearSpectrum {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct BlackbodySpectrum { pub struct BlackbodySpectrum {
temperature: Float, pub temperature: Float,
normalization_factor: Float, pub normalization_factor: Float,
} }
// Planck's Law // Planck's Law
@ -331,150 +245,3 @@ impl SpectrumTrait for BlackbodySpectrum {
Self::planck_law(lambda_max, self.temperature) Self::planck_law(lambda_max, self.temperature)
} }
} }
pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| {
let mut m = HashMap::new();
// A macro to reduce boilerplate and make the list readable
macro_rules! add {
($name:expr, $data:expr, $norm:expr) => {
m.insert(
$name.to_string(),
Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum::from_interleaved($data, $norm)),
);
};
}
add!("stdillum-A", &CIE_ILLUM_A, true);
add!("stdillum-D50", &CIE_ILLUM_D5000, true);
add!("stdillum-D65", &CIE_ILLUM_D6500, true);
add!("stdillum-F1", &CIE_ILLUM_F1, true);
add!("stdillum-F2", &CIE_ILLUM_F2, true);
add!("stdillum-F3", &CIE_ILLUM_F3, true);
add!("stdillum-F4", &CIE_ILLUM_F4, true);
add!("stdillum-F5", &CIE_ILLUM_F5, true);
add!("stdillum-F6", &CIE_ILLUM_F6, true);
add!("stdillum-F7", &CIE_ILLUM_F7, true);
add!("stdillum-F8", &CIE_ILLUM_F8, true);
add!("stdillum-F9", &CIE_ILLUM_F9, true);
add!("stdillum-F10", &CIE_ILLUM_F10, true);
add!("stdillum-F11", &CIE_ILLUM_F11, true);
add!("stdillum-F12", &CIE_ILLUM_F12, true);
add!("illum-acesD60", &ACES_ILLUM_D60, true);
// --- Glasses ---
add!("glass-BK7", &GLASS_BK7_ETA, false);
add!("glass-BAF10", &GLASS_BAF10_ETA, false);
add!("glass-FK51A", &GLASS_FK51A_ETA, false);
add!("glass-LASF9", &GLASS_LASF9_ETA, false);
add!("glass-F5", &GLASS_SF5_ETA, false);
add!("glass-F10", &GLASS_SF10_ETA, false);
add!("glass-F11", &GLASS_SF11_ETA, false);
// --- Metals ---
add!("metal-Ag-eta", &AG_ETA, false);
add!("metal-Ag-k", &AG_K, false);
add!("metal-Al-eta", &AL_ETA, false);
add!("metal-Al-k", &AL_K, false);
add!("metal-Au-eta", &AU_ETA, false);
add!("metal-Au-k", &AU_K, false);
add!("metal-Cu-eta", &CU_ETA, false);
add!("metal-Cu-k", &CU_K, false);
add!("metal-CuZn-eta", &CUZN_ETA, false);
add!("metal-CuZn-k", &CUZN_K, false);
add!("metal-MgO-eta", &MGO_ETA, false);
add!("metal-MgO-k", &MGO_K, false);
add!("metal-TiO2-eta", &TIO2_ETA, false);
add!("metal-TiO2-k", &TIO2_K, false);
// --- Canon EOS 100D ---
add!("canon_eos_100d_r", &CANON_EOS_100D_R, false);
add!("canon_eos_100d_g", &CANON_EOS_100D_G, false);
add!("canon_eos_100d_b", &CANON_EOS_100D_B, false);
// --- Canon EOS 1DX MkII ---
add!("canon_eos_1dx_mkii_r", &CANON_EOS_1DX_MKII_R, false);
add!("canon_eos_1dx_mkii_g", &CANON_EOS_1DX_MKII_G, false);
add!("canon_eos_1dx_mkii_b", &CANON_EOS_1DX_MKII_B, false);
// --- Canon EOS 200D ---
add!("canon_eos_200d_r", &CANON_EOS_200D_R, false);
add!("canon_eos_200d_g", &CANON_EOS_200D_G, false);
add!("canon_eos_200d_b", &CANON_EOS_200D_B, false);
// --- Canon EOS 200D MkII ---
add!("canon_eos_200d_mkii_r", &CANON_EOS_200D_MKII_R, false);
add!("canon_eos_200d_mkii_g", &CANON_EOS_200D_MKII_G, false);
add!("canon_eos_200d_mkii_b", &CANON_EOS_200D_MKII_B, false);
// --- Canon EOS 5D ---
add!("canon_eos_5d_r", &CANON_EOS_5D_R, false);
add!("canon_eos_5d_g", &CANON_EOS_5D_G, false);
add!("canon_eos_5d_b", &CANON_EOS_5D_B, false);
// --- Canon EOS 5D MkII ---
add!("canon_eos_5d_mkii_r", &CANON_EOS_5D_MKII_R, false);
add!("canon_eos_5d_mkii_g", &CANON_EOS_5D_MKII_G, false);
add!("canon_eos_5d_mkii_b", &CANON_EOS_5D_MKII_B, false);
// --- Canon EOS 5D MkIII ---
add!("canon_eos_5d_mkiii_r", &CANON_EOS_5D_MKIII_R, false);
add!("canon_eos_5d_mkiii_g", &CANON_EOS_5D_MKIII_G, false);
add!("canon_eos_5d_mkiii_b", &CANON_EOS_5D_MKIII_B, false);
// --- Canon EOS 5D MkIV ---
add!("canon_eos_5d_mkiv_r", &CANON_EOS_5D_MKIV_R, false);
add!("canon_eos_5d_mkiv_g", &CANON_EOS_5D_MKIV_G, false);
add!("canon_eos_5d_mkiv_b", &CANON_EOS_5D_MKIV_B, false);
// --- Canon EOS 5DS ---
add!("canon_eos_5ds_r", &CANON_EOS_5DS_R, false);
add!("canon_eos_5ds_g", &CANON_EOS_5DS_G, false);
add!("canon_eos_5ds_b", &CANON_EOS_5DS_B, false);
// --- Canon EOS M ---
add!("canon_eos_m_r", &CANON_EOS_M_R, false);
add!("canon_eos_m_g", &CANON_EOS_M_G, false);
add!("canon_eos_m_b", &CANON_EOS_M_B, false);
// --- Hasselblad L1D 20C ---
add!("hasselblad_l1d_20c_r", &HASSELBLAD_L1D_20C_R, false);
add!("hasselblad_l1d_20c_g", &HASSELBLAD_L1D_20C_G, false);
add!("hasselblad_l1d_20c_b", &HASSELBLAD_L1D_20C_B, false);
// --- Nikon D810 ---
add!("nikon_d810_r", &NIKON_D810_R, false);
add!("nikon_d810_g", &NIKON_D810_G, false);
add!("nikon_d810_b", &NIKON_D810_B, false);
// --- Nikon D850 ---
add!("nikon_d850_r", &NIKON_D850_R, false);
add!("nikon_d850_g", &NIKON_D850_G, false);
add!("nikon_d850_b", &NIKON_D850_B, false);
// --- Sony ILCE 6400 ---
add!("sony_ilce_6400_r", &SONY_ILCE_6400_R, false);
add!("sony_ilce_6400_g", &SONY_ILCE_6400_G, false);
add!("sony_ilce_6400_b", &SONY_ILCE_6400_B, false);
// --- Sony ILCE 7M3 ---
add!("sony_ilce_7m3_r", &SONY_ILCE_7M3_R, false);
add!("sony_ilce_7m3_g", &SONY_ILCE_7M3_G, false);
add!("sony_ilce_7m3_b", &SONY_ILCE_7M3_B, false);
// --- Sony ILCE 7RM3 ---
add!("sony_ilce_7rm3_r", &SONY_ILCE_7RM3_R, false);
add!("sony_ilce_7rm3_g", &SONY_ILCE_7RM3_G, false);
add!("sony_ilce_7rm3_b", &SONY_ILCE_7RM3_B, false);
// --- Sony ILCE 9 ---
add!("sony_ilce_9_r", &SONY_ILCE_9_R, false);
add!("sony_ilce_9_g", &SONY_ILCE_9_G, false);
add!("sony_ilce_9_b", &SONY_ILCE_9_B, false);
m
});
pub fn get_named_spectrum(name: &str) -> Option<Spectrum> {
NAMED_SPECTRA.get(name).cloned()
}

View file

@ -1,14 +1,33 @@
use crate::core::geometry::Vector3f;
use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
use crate::utils::Ptr;
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub struct GPUFloatMixTexture { pub struct GPUFloatMixTexture {
tex1: GPUFloatTexture, pub tex1: Ptr<GPUFloatTexture>,
tex2: GPUFloatTexture, pub tex2: Ptr<GPUFloatTexture>,
} }
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub struct GPUFloatDirectionMixTexture { pub struct GPUFloatDirectionMixTexture {
tex1: GPUFloatTexture, pub tex1: Ptr<GPUFloatTexture>,
tex2: GPUFloatTexture, pub tex2: Ptr<GPUFloatTexture>,
dir: Vector3f, pub dir: Vector3f,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct GPUSpectrumMixTexture {
pub tex1: Ptr<GPUSpectrumTexture>,
pub tex2: Ptr<GPUSpectrumTexture>,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct GPUSpectrumDirectionMixTexture {
pub tex1: Ptr<GPUSpectrumTexture>,
pub tex2: Ptr<GPUSpectrumTexture>,
pub dir: Vector3f,
} }

View file

@ -7,6 +7,7 @@ pub mod image;
pub mod marble; pub mod marble;
pub mod mix; pub mod mix;
pub mod ptex; pub mod ptex;
pub mod scaled;
pub mod windy; pub mod windy;
pub mod wrinkled; pub mod wrinkled;
@ -19,5 +20,6 @@ pub use image::*;
pub use marble::*; pub use marble::*;
pub use mix::*; pub use mix::*;
pub use ptex::*; pub use ptex::*;
pub use scaled::*;
pub use windy::*; pub use windy::*;
pub use wrinkled::*; pub use wrinkled::*;

View 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>,
}

View file

@ -180,31 +180,40 @@ impl<T> IndexMut<(i32, i32)> for Array2D<T> {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SampledGrid<T> { pub struct SampledGrid<T> {
values: Vec<T>, pub values: *const T,
nx: i32, pub nx: i32,
ny: i32, pub ny: i32,
nz: i32, pub nz: i32,
} }
unsafe impl<T: Sync> Sync for SampledGrid<T> {}
unsafe impl<T: Send> Send for SampledGrid<T> {}
impl<T> SampledGrid<T> { impl<T> SampledGrid<T> {
pub fn new(values: Vec<T>, nx: i32, ny: i32, nz: i32) -> Self { #[cfg(not(target_os = "cuda"))]
assert_eq!( pub fn new(slice: &[T], nx: i32, ny: i32, nz: i32) -> Self {
values.len(), assert_eq!(slice.len(), (nx * ny * nz) as usize);
(nx * ny * nz) as usize, Self {
"Grid dimensions do not match data size" values: slice.as_ptr(),
); nx,
Self { values, nx, ny, nz } ny,
nz,
}
} }
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
values: Vec::new(), values: core::ptr::null(),
nx: 0, nx: 0,
ny: 0, ny: 0,
nz: 0, nz: 0,
} }
} }
pub fn is_valid(&self) -> bool {
!self.values.is_null() && self.nx > 0 && self.ny > 0 && self.nz > 0
}
pub fn bytes_allocated(&self) -> usize { pub fn bytes_allocated(&self) -> usize {
self.values.len() * std::mem::size_of::<T>() self.values.len() * std::mem::size_of::<T>()
} }
@ -212,9 +221,11 @@ impl<T> SampledGrid<T> {
pub fn x_size(&self) -> i32 { pub fn x_size(&self) -> i32 {
self.nx self.nx
} }
pub fn y_size(&self) -> i32 { pub fn y_size(&self) -> i32 {
self.ny self.ny
} }
pub fn z_size(&self) -> i32 { pub fn z_size(&self) -> i32 {
self.nz self.nz
} }
@ -224,7 +235,11 @@ impl<T> SampledGrid<T> {
F: Fn(&T) -> U, F: Fn(&T) -> U,
U: Default, U: Default,
{ {
let sample_bounds = Bounds3i::from_points( if !self.is_valid() {
return U::default();
}
let sample_bounds = Bounds3i::new(
Point3i::new(0, 0, 0), Point3i::new(0, 0, 0),
Point3i::new(self.nx, self.ny, self.nz), Point3i::new(self.nx, self.ny, self.nz),
); );
@ -234,7 +249,10 @@ impl<T> SampledGrid<T> {
} }
let idx = (p.z() * self.ny + p.y()) * self.nx + p.x(); let idx = (p.z() * self.ny + p.y()) * self.nx + p.x();
convert(&self.values[idx as usize]) unsafe {
let val = &*self.values.add(idx as usize);
convert(val)
}
} }
pub fn lookup_int(&self, p: Point3i) -> T pub fn lookup_int(&self, p: Point3i) -> T
@ -249,6 +267,10 @@ impl<T> SampledGrid<T> {
F: Fn(&T) -> U + Copy, F: Fn(&T) -> U + Copy,
U: Interpolatable + Default, U: Interpolatable + Default,
{ {
if !self.is_valid() {
return U::default();
}
let p_samples = Point3f::new( let p_samples = Point3f::new(
p.x() * self.nx as Float - 0.5, p.x() * self.nx as Float - 0.5,
p.y() * self.ny as Float - 0.5, p.y() * self.ny as Float - 0.5,
@ -298,6 +320,10 @@ impl<T> SampledGrid<T> {
F: Fn(&T) -> U, F: Fn(&T) -> U,
U: PartialOrd + Default, U: PartialOrd + Default,
{ {
if !self.is_valid() {
return U::default();
}
let ps = [ let ps = [
Point3f::new( Point3f::new(
bounds.p_min.x() * self.nx as Float - 0.5, bounds.p_min.x() * self.nx as Float - 0.5,
@ -318,7 +344,6 @@ impl<T> SampledGrid<T> {
self.nz - 1, self.nz - 1,
)); ));
// Initialize with the first voxel
let mut max_value = self.lookup_int_convert(pi_min, &convert); let mut max_value = self.lookup_int_convert(pi_min, &convert);
for z in pi_min.z()..=pi_max.z() { for z in pi_min.z()..=pi_max.z() {
@ -342,30 +367,3 @@ impl<T> SampledGrid<T> {
self.max_value_convert(bounds, |v| v.clone()) self.max_value_convert(bounds, |v| v.clone())
} }
} }
pub struct InternCache<T> {
cache: Mutex<HashSet<Arc<T>>>,
}
impl<T> InternCache<T>
where
T: Eq + Hash + Clone,
{
pub fn new() -> Self {
Self {
cache: Mutex::new(HashSet::new()),
}
}
pub fn lookup(&self, value: T) -> Arc<T> {
let mut lock = self.cache.lock().unwrap();
if let Some(existing) = lock.get(&value) {
return existing.clone(); // Returns a cheap Arc copy
}
let new_item = Arc::new(value);
lock.insert(new_item.clone());
new_item
}
}

View file

@ -6,7 +6,6 @@ use crate::utils::hash::{hash_buffer, mix_bits};
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
use num_traits::{Float as NumFloat, Num, One, Signed, Zero}; use num_traits::{Float as NumFloat, Num, One, Signed, Zero};
use rayon::prelude::*;
use std::error::Error; use std::error::Error;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::iter::{Product, Sum}; use std::iter::{Product, Sum};
@ -64,16 +63,13 @@ where
} }
#[inline] #[inline]
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> { pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
if coeffs.is_empty() { assert!(!coeffs.is_empty());
return None;
}
let mut result = coeffs[0]; let mut result = coeffs[0];
for &c in &coeffs[1..] { for &c in &coeffs[1..] {
result = t.mul_add(result, c); result = t.mul_add(result, c);
} }
Some(result) result
} }
#[inline] #[inline]
@ -913,6 +909,7 @@ pub fn multiply_generator(c: &[u32], mut a: u32) -> u32 {
v v
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct NoRandomizer; pub struct NoRandomizer;
impl NoRandomizer { impl NoRandomizer {
@ -921,6 +918,7 @@ impl NoRandomizer {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct BinaryPermuteScrambler { pub struct BinaryPermuteScrambler {
pub permutation: u32, pub permutation: u32,
@ -937,6 +935,7 @@ impl BinaryPermuteScrambler {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct FastOwenScrambler { pub struct FastOwenScrambler {
pub seed: u32, pub seed: u32,
@ -970,6 +969,7 @@ impl FastOwenScrambler {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct OwenScrambler { pub struct OwenScrambler {
pub seed: u32, pub seed: u32,
@ -1096,9 +1096,10 @@ pub fn sobol_interval_to_index(m: u32, frame: u64, p: Point2i) -> u64 {
} }
// MATRIX STUFF (TEST THOROUGHLY) // MATRIX STUFF (TEST THOROUGHLY)
#[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Matrix<T, const R: usize, const C: usize> { pub struct Matrix<T, const R: usize, const C: usize> {
m: [[T; C]; R], pub m: [[T; C]; R],
} }
impl<T, const R: usize, const C: usize> Matrix<T, R, C> { impl<T, const R: usize, const C: usize> Matrix<T, R, C> {
@ -1261,6 +1262,7 @@ where
} }
pub type SquareMatrix<T, const N: usize> = Matrix<T, N, N>; pub type SquareMatrix<T, const N: usize> = Matrix<T, N, N>;
pub type SquareMatrix3f = SquareMatrix<Float, 3>;
impl<T, const N: usize> SquareMatrix<T, N> { impl<T, const N: usize> SquareMatrix<T, N> {
pub fn identity() -> Self pub fn identity() -> Self

View file

@ -7,6 +7,7 @@ pub mod interval;
pub mod math; pub mod math;
pub mod mesh; pub mod mesh;
pub mod noise; pub mod noise;
pub mod ptr;
pub mod quaternion; pub mod quaternion;
pub mod rng; pub mod rng;
pub mod sampling; pub mod sampling;
@ -14,6 +15,7 @@ pub mod sobol;
pub mod splines; pub mod splines;
pub mod transform; pub mod transform;
pub use ptr::Ptr;
pub use transform::{AnimatedTransform, Transform, TransformGeneric}; pub use transform::{AnimatedTransform, Transform, TransformGeneric};
#[inline] #[inline]

49
shared/src/utils/ptr.rs Normal file
View 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,
}
}
}

View file

@ -1,11 +1,11 @@
use std::f32::consts::PI;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use crate::core::geometry::{Vector3f, VectorLike}; use crate::core::geometry::{Vector3f, VectorLike};
use crate::core::pbrt::Float;
use crate::utils::math::{safe_asin, sinx_over_x}; use crate::utils::math::{safe_asin, sinx_over_x};
use crate::{Float, PI};
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct Quaternion { pub struct Quaternion {
pub v: Vector3f, pub v: Vector3f,

View file

@ -5,8 +5,9 @@ use std::iter::{Product, Sum};
use std::ops::{Add, Div, Index, IndexMut, Mul}; use std::ops::{Add, Div, Index, IndexMut, Mul};
use std::sync::Arc; use std::sync::Arc;
use super::math::{radians, safe_acos, SquareMatrix}; use super::math::{SquareMatrix, radians, safe_acos};
use super::quaternion::Quaternion; use super::quaternion::Quaternion;
use crate::core::color::{RGB, XYZ};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi, Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi,
VectorLike, VectorLike,
@ -14,10 +15,10 @@ use crate::core::geometry::{
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction, Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction,
}; };
use crate::core::pbrt::{gamma, Float}; use crate::core::pbrt::{Float, gamma};
use crate::spectra::{RGB, XYZ};
use crate::utils::error::InversionError; use crate::utils::error::InversionError;
#[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct TransformGeneric<T: NumFloat> { pub struct TransformGeneric<T: NumFloat> {
m: SquareMatrix<T, 4>, m: SquareMatrix<T, 4>,
@ -754,6 +755,7 @@ impl From<Quaternion> for TransformGeneric<Float> {
} }
} }
#[repr(C)]
#[derive(Default, Debug, Copy, Clone)] #[derive(Default, Debug, Copy, Clone)]
pub struct DerivativeTerm { pub struct DerivativeTerm {
kc: Float, kc: Float,
@ -777,7 +779,8 @@ impl DerivativeTerm {
} }
} }
#[derive(Debug, Default, Clone)] #[repr(C)]
#[derive(Debug, Copy, Default, Clone)]
pub struct AnimatedTransform { pub struct AnimatedTransform {
pub start_transform: Transform, pub start_transform: Transform,
pub end_transform: Transform, pub end_transform: Transform,

413
src/core/camera.rs Normal file
View 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
View 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],
]);

View file

@ -88,7 +88,7 @@ impl FilmBaseHost for FilmBase {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
filter: Filter, filter: Filter,
sensor: Option<PixelSensor>, sensor: Option<&PixelSensor>,
loc: &FileLoc, loc: &FileLoc,
) -> Self { ) -> Self {
let x_res = params.get_one_int("xresolution", 1280); let x_res = params.get_one_int("xresolution", 1280);

View file

@ -1,4 +1,7 @@
pub mod camera;
pub mod color;
pub mod film; pub mod film;
pub mod filter; pub mod filter;
pub mod scene; pub mod scene;
pub mod spectrum;
pub mod texture; pub mod texture;

8
src/core/spectrum.rs Normal file
View 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)
}

View file

@ -250,41 +250,3 @@ struct TexInfo {
wrap_mode: WrapMode, wrap_mode: WrapMode,
encoding: ColorEncoding, encoding: ColorEncoding,
} }
pub trait TextureEvaluator: Send + Sync {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float;
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum;
fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool;
}
#[derive(Copy, Clone, Default)]
pub struct UniversalTextureEvaluator;
impl TextureEvaluator for UniversalTextureEvaluator {
fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float {
tex.evaluate(ctx)
}
fn evaluate_spectrum(
&self,
tex: &SpectrumTexture,
ctx: &TextureEvalContext,
lambda: &SampledWavelengths,
) -> SampledSpectrum {
tex.evaluate(ctx, lambda)
}
fn can_evaluate(
&self,
_float_textures: &[&FloatTexture],
_spectrum_textures: &[&SpectrumTexture],
) -> bool {
true
}
}

View file

@ -1,4 +1,5 @@
pub mod core; pub mod core;
pub mod lights; pub mod lights;
pub mod spectra;
pub mod textures; pub mod textures;
pub mod utils; pub mod utils;

58
src/spectra/colorspace.rs Normal file
View 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
View 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
View 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
View 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
View 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))
}
}

View file

@ -1,25 +1,9 @@
pub mod bilerp;
pub mod checkerboard;
pub mod constant;
pub mod dots;
pub mod fbm;
pub mod image; pub mod image;
pub mod marble;
pub mod mix; pub mod mix;
pub mod ptex; pub mod ptex;
pub mod scale; pub mod scaled;
pub mod windy;
pub mod wrinkled;
pub use bilerp::*;
pub use checkerboard::*;
pub use constant::*;
pub use dots::*;
pub use fbm::*;
pub use image::*; pub use image::*;
pub use marble::*;
pub use mix::*; pub use mix::*;
pub use ptex::*; pub use ptex::*;
pub use scale::*; pub use scaled::*;
pub use windy::*;
pub use wrinkled::*;

26
src/utils/containers.rs Normal file
View 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
}
}

View file

@ -16,6 +16,31 @@ fn set_search_directory(filename: &str) {
let _ = SEARCH_DIRECTORY.set(dir); let _ = SEARCH_DIRECTORY.set(dir);
} }
pub fn resolve_filename(filename: &str) -> String {
let search_directory = SEARCH_DIRECTORY
.get()
.map(|p| p.as_path())
.unwrap_or(Path::new(""));
if search_directory.as_os_str().is_empty()
|| filename.is_empty()
|| Path::new(filename).is_absolute()
{
return filename.to_string();
}
let filepath = search_directory.join(filename);
if filepath.exists() {
filepath
.canonicalize()
.unwrap_or_else(|_| filepath.to_path_buf())
.to_string_lossy()
.to_string()
} else {
filename.to_string()
}
}
pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> { pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
let content = fs::read_to_string(filename)?; let content = fs::read_to_string(filename)?;
@ -45,28 +70,3 @@ pub fn read_float_file(filename: &str) -> io::Result<Vec<Float>> {
Ok(values) Ok(values)
} }
pub fn resolve_filename(filename: &str) -> String {
let search_directory = SEARCH_DIRECTORY
.get()
.map(|p| p.as_path())
.unwrap_or(Path::new(""));
if search_directory.as_os_str().is_empty()
|| filename.is_empty()
|| Path::new(filename).is_absolute()
{
return filename.to_string();
}
let filepath = search_directory.join(filename);
if filepath.exists() {
filepath
.canonicalize()
.unwrap_or_else(|_| filepath.to_path_buf())
.to_string_lossy()
.to_string()
} else {
filename.to_string()
}
}

View file

@ -1,3 +1,4 @@
pub mod containers;
pub mod error; pub mod error;
pub mod file; pub mod file;
pub mod io; pub mod io;