From 4dbec9bc2c23dd0067d047082e9b30e7e914df38 Mon Sep 17 00:00:00 2001 From: pingu Date: Mon, 22 Dec 2025 22:54:49 +0000 Subject: [PATCH] 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. --- Cargo.toml | 6 +- kernels/Cargo.toml | 2 +- shared/src/cameras/orthographic.rs | 119 ++--- shared/src/cameras/perspective.rs | 66 +-- shared/src/cameras/realistic.rs | 449 +++++++---------- shared/src/cameras/spherical.rs | 96 +--- shared/src/core/aggregates.rs | 6 +- shared/src/core/camera.rs | 254 ++++------ shared/src/{spectra => core}/color.rs | 320 ++++++------ shared/src/core/film.rs | 77 ++- shared/src/core/filter.rs | 61 +-- shared/src/core/geometry/ray.rs | 11 +- shared/src/core/interaction.rs | 104 ++-- shared/src/core/light.rs | 690 +++----------------------- shared/src/core/material.rs | 175 ++++--- shared/src/core/medium.rs | 182 ++++--- shared/src/core/mod.rs | 1 + shared/src/core/primitive.rs | 25 +- shared/src/core/spectrum.rs | 70 +++ shared/src/core/texture.rs | 102 +++- shared/src/filters/mod.rs | 6 + shared/src/integrators/mod.rs | 6 +- shared/src/lights/diffuse.rs | 253 +++++++++- shared/src/lights/distant.rs | 101 ++++ shared/src/lights/goniometric.rs | 109 ++++ shared/src/lights/infinite.rs | 110 ++-- shared/src/lights/mod.rs | 12 + shared/src/lights/point.rs | 103 ++++ shared/src/lights/projection.rs | 169 +++++++ shared/src/lights/sampler.rs | 10 +- shared/src/lights/spot.rs | 111 +++++ shared/src/spectra/cie.rs | 108 ++++ shared/src/spectra/colorspace.rs | 137 +---- shared/src/spectra/data.rs | 65 --- shared/src/spectra/mod.rs | 83 ---- shared/src/spectra/rgb.rs | 90 ++-- shared/src/spectra/sampled.rs | 20 +- shared/src/spectra/simple.rs | 325 ++---------- shared/src/textures/mix.rs | 33 +- shared/src/textures/mod.rs | 2 + shared/src/textures/scaled.rs | 16 + shared/src/utils/containers.rs | 82 ++- shared/src/utils/math.rs | 18 +- shared/src/utils/mod.rs | 2 + shared/src/utils/ptr.rs | 49 ++ shared/src/utils/quaternion.rs | 4 +- shared/src/utils/transform.rs | 11 +- src/core/camera.rs | 413 +++++++++++++++ src/core/color.rs | 47 ++ src/core/film.rs | 2 +- src/core/mod.rs | 3 + src/core/spectrum.rs | 8 + src/core/texture.rs | 38 -- src/lib.rs | 1 + src/spectra/colorspace.rs | 58 +++ src/spectra/data.rs | 162 ++++++ src/spectra/dense.rs | 112 +++++ src/spectra/mod.rs | 109 ++++ src/spectra/piecewise.rs | 77 +++ src/textures/mod.rs | 20 +- src/textures/{scale.rs => scaled.rs} | 0 src/utils/containers.rs | 26 + src/utils/file.rs | 50 +- src/utils/mod.rs | 1 + 64 files changed, 3408 insertions(+), 2570 deletions(-) rename shared/src/{spectra => core}/color.rs (79%) create mode 100644 shared/src/lights/distant.rs create mode 100644 shared/src/lights/goniometric.rs create mode 100644 shared/src/lights/point.rs create mode 100644 shared/src/lights/projection.rs create mode 100644 shared/src/lights/spot.rs delete mode 100644 shared/src/spectra/data.rs create mode 100644 shared/src/textures/scaled.rs create mode 100644 shared/src/utils/ptr.rs create mode 100644 src/core/camera.rs create mode 100644 src/core/color.rs create mode 100644 src/core/spectrum.rs create mode 100644 src/spectra/colorspace.rs create mode 100644 src/spectra/data.rs create mode 100644 src/spectra/dense.rs create mode 100644 src/spectra/mod.rs create mode 100644 src/spectra/piecewise.rs rename src/textures/{scale.rs => scaled.rs} (100%) create mode 100644 src/utils/containers.rs diff --git a/Cargo.toml b/Cargo.toml index 62093b6..4558311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,7 @@ edition = "2024" [features] default = [] use_f64 = [] -cuda = ["cuda_std", "cust", "cuda_builder", "shared/cuda", ] -use_nvtx = [] +cuda = ["cust", "cuda_builder", "shared/cuda", ] [dependencies] anyhow = "1.0.100" @@ -35,6 +34,9 @@ kernels = { path = "kernels" } cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, features = ["glam"], optional = true } +ptex = "0.3.0" +ptex-sys = "0.3.0" +slice = "0.0.4" [build-dependencies] spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true } diff --git a/kernels/Cargo.toml b/kernels/Cargo.toml index 0a5f97c..e184d53 100644 --- a/kernels/Cargo.toml +++ b/kernels/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] cuda_std = { git = "https://github.com/rust-gpu/rust-cuda", rev = "7fa76f3d717038a92c90bf4a482b0b8dd3259344" } -shared = { path = "../shared" } +shared = { path = "../shared", features = ["cuda"] } [lib] crate-type = ["cdylib", "rlib"] diff --git a/shared/src/cameras/orthographic.rs b/shared/src/cameras/orthographic.rs index 56604fd..dbecd56 100644 --- a/shared/src/cameras/orthographic.rs +++ b/shared/src/cameras/orthographic.rs @@ -1,34 +1,31 @@ -use crate::core::camera::{ - CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, -}; -use crate::core::film::{Film, FilmTrait}; +use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; +use crate::core::film::Film; use crate::core::geometry::{ Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike, }; use crate::core::medium::Medium; -use crate::core::options::get_options; use crate::core::pbrt::Float; use crate::core::sampler::CameraSample; +use crate::images::ImageMetadata; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::error::FileLoc; -use crate::utils::parameters::ParameterDictionary; +use crate::utils::Transform; use crate::utils::sampling::sample_uniform_disk_concentric; -use crate::utils::transform::TransformGeneric; -use std::sync::Arc; -#[derive(Debug)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct OrthographicCamera { pub base: CameraBase, - pub screen_from_camera: TransformGeneric, - pub camera_from_raster: TransformGeneric, - pub raster_from_screen: TransformGeneric, - pub screen_from_raster: TransformGeneric, + pub screen_from_camera: Transform, + pub camera_from_raster: Transform, + pub raster_from_screen: Transform, + pub screen_from_raster: Transform, pub lens_radius: Float, pub focal_distance: Float, pub dx_camera: Vector3f, pub dy_camera: Vector3f, } +#[cfg(not(target_os = "cuda"))] impl OrthographicCamera { pub fn new( base: CameraBase, @@ -36,21 +33,30 @@ impl OrthographicCamera { lens_radius: Float, focal_distance: Float, ) -> Self { - let ndc_from_screen: TransformGeneric = TransformGeneric::scale( + let ndc_from_screen: Transform = Transform::scale( 1. / (screen_window.p_max.x() - screen_window.p_min.x()), 1. / (screen_window.p_max.y() - screen_window.p_min.y()), 1., - ) * TransformGeneric::translate( - Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.), - ); - let raster_from_ndc = TransformGeneric::scale( - base.film.full_resolution().x() as Float, - -base.film.full_resolution().y() as Float, + ) * Transform::translate(Vector3f::new( + -screen_window.p_min.x(), + -screen_window.p_max.y(), + 0., + )); + let film_ptr = base.film; + if film_ptr.is_null() { + panic!("Camera must have a film"); + } + let film = unsafe { &*film_ptr }; + + let raster_from_ndc = Transform::scale( + film.full_resolution().x() as Float, + -film.full_resolution().y() as Float, 1., ); + let raster_from_screen = raster_from_ndc * ndc_from_screen; let screen_from_raster = raster_from_screen.inverse(); - let screen_from_camera = TransformGeneric::orthographic(0., 1.); + let screen_from_camera = Transform::orthographic(0., 1.); let camera_from_raster = screen_from_camera.inverse() * screen_from_raster; let dx_camera = camera_from_raster.apply_to_vector(Vector3f::new(1., 0., 0.)); let dy_camera = camera_from_raster.apply_to_vector(Vector3f::new(0., 1., 0.)); @@ -71,58 +77,16 @@ impl OrthographicCamera { dy_camera, } } - - pub fn create( - params: &ParameterDictionary, - camera_transform: &CameraTransform, - film: Arc, - medium: Medium, - loc: &FileLoc, - ) -> Result { - let full_res = film.full_resolution(); - let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); - let base = CameraBase::new(camera_params); - let lens_radius = params.get_one_float("lensradius", 0.); - let focal_distance = params.get_one_float("focaldistance", 1e6); - let frame = params.get_one_float( - "frameaspectratio", - full_res.x() as Float / full_res.y() as Float, - ); - - let mut screen = if frame > 1. { - Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) - } else { - Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame)) - }; - - let sw = params.get_float_array("screenwindow"); - if !sw.is_empty() { - if get_options().fullscreen { - eprint!("Screenwindow is ignored in fullscreen mode"); - } else { - if sw.len() == 4 { - screen = Bounds2f::from_points( - Point2f::new(sw[0], sw[2]), - Point2f::new(sw[1], sw[3]), - ); - } else { - return Err(format!("{}: screenwindow param must have four values", loc)); - } - } - } - - Ok(Self::new(base, screen, lens_radius, focal_distance)) - } } impl CameraTrait for OrthographicCamera { - fn base(&self) -> &CameraBase { - &self.base + #[cfg(not(target_os = "cuda"))] + fn init_metadata(&self, metadata: &mut ImageMetadata) { + self.base.init_metadata(metadata) } - fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { - self.base.init_metadata(metadata) + fn base(&self) -> &CameraBase { + &self.base } fn generate_ray( @@ -140,18 +104,14 @@ impl CameraTrait for OrthographicCamera { Some(self.sample_time(sample.time)), self.base().medium.clone(), ); - // Modify ray for depth of field if self.lens_radius > 0. { - // Sample point on lens let p_lens_vec = self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens)); let p_lens = Point2f::from(p_lens_vec); - // Compute point on plane of focus let ft = self.focal_distance / ray.d.z(); let p_focus = ray.at(ft); - // Update ray for effect of lens ray.o = Point3f::new(p_lens.x(), p_lens.y(), 0.); ray.d = (p_focus - ray.o).normalize(); } @@ -172,7 +132,18 @@ impl CameraTrait for OrthographicCamera { let mut central_cam_ray = self.generate_ray(sample, lambda)?; let mut rd = RayDifferential::default(); if self.lens_radius > 0.0 { - return self.generate_ray_differential(sample, lambda); + let mut sample_x = sample; + sample_x.p_film.x += 1.0; + let rx = self.generate_ray(sample_x, lambda)?; + + let mut sample_y = sample; + sample_y.p_film.y += 1.0; + let ry = self.generate_ray(sample_y, lambda)?; + + rd.rx_origin = rx.ray.o; + rd.ry_origin = ry.ray.o; + rd.rx_direction = rx.ray.d; + rd.ry_direction = ry.ray.d; } else { let time = self.sample_time(sample.time); let world_dx = self diff --git a/shared/src/cameras/perspective.rs b/shared/src/cameras/perspective.rs index 4ddb38e..f602013 100644 --- a/shared/src/cameras/perspective.rs +++ b/shared/src/cameras/perspective.rs @@ -1,20 +1,14 @@ -use super::{CameraBase, CameraRay, CameraTrait, CameraTransform}; -use crate::camera::CameraBaseParameters; -use crate::core::film::{Film, FilmTrait}; -use crate::core::filter::FilterTrait; +use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; +use crate::core::film::Film; use crate::core::geometry::{ Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike, }; use crate::core::medium::Medium; -use crate::core::options::get_options; use crate::core::pbrt::Float; use crate::core::sampler::CameraSample; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::error::FileLoc; -use crate::utils::parameters::ParameterDictionary; use crate::utils::sampling::sample_uniform_disk_concentric; use crate::utils::transform::Transform; -use std::sync::Arc; #[derive(Debug)] pub struct PerspectiveCamera { @@ -30,6 +24,7 @@ pub struct PerspectiveCamera { pub cos_total_width: Float, } +#[cfg(not(target_os = "cuda"))] impl PerspectiveCamera { pub fn new( base: CameraBase, @@ -80,61 +75,18 @@ impl PerspectiveCamera { cos_total_width, } } - - pub fn create( - params: &ParameterDictionary, - camera_transform: &CameraTransform, - film: Arc, - medium: Medium, - loc: &FileLoc, - ) -> Result { - let full_res = film.full_resolution(); - let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); - let base = CameraBase::new(camera_params); - let lens_radius = params.get_one_float("lensradius", 0.); - let focal_distance = params.get_one_float("focaldistance", 1e6); - let frame = params.get_one_float( - "frameaspectratio", - full_res.x() as Float / full_res.y() as Float, - ); - - let mut screen = if frame > 1. { - Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) - } else { - Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame)) - }; - - let sw = params.get_float_array("screenwindow"); - if !sw.is_empty() { - if get_options().fullscreen { - eprint!("Screenwindow is ignored in fullscreen mode"); - } else { - if sw.len() == 4 { - screen = Bounds2f::from_points( - Point2f::new(sw[0], sw[2]), - Point2f::new(sw[1], sw[3]), - ); - } else { - return Err(format!("{}: screenwindow param must have four values", loc)); - } - } - } - - let fov = params.get_one_float("fov", 90.); - Ok(Self::new(base, fov, screen, lens_radius, focal_distance)) - } } -impl CameraTrait for PerspectiveCamera { - fn base(&self) -> &CameraBase { - &self.base - } - +impl PerspectiveCamera { + #[cfg(not(target_os = "cuda"))] fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { self.base.init_metadata(metadata) } + fn base(&self) -> &CameraBase { + &self.base + } + fn generate_ray( &self, sample: CameraSample, diff --git a/shared/src/cameras/realistic.rs b/shared/src/cameras/realistic.rs index a2be62e..739cd9d 100644 --- a/shared/src/cameras/realistic.rs +++ b/shared/src/cameras/realistic.rs @@ -1,9 +1,6 @@ -use super::{ - CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform, ExitPupilSample, - LensElementInterface, -}; use crate::PI; -use crate::core::film::{Film, FilmTrait}; +use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; +use crate::core::film::Film; use crate::core::geometry::{ Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike, }; @@ -11,38 +8,59 @@ use crate::core::medium::Medium; use crate::core::pbrt::Float; use crate::core::sampler::CameraSample; use crate::core::scattering::refract; -use crate::image::{Image, PixelFormat}; +use crate::images::{Image, PixelFormat}; use crate::spectra::color::SRGB; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::error::FileLoc; -use crate::utils::file::read_float_file; use crate::utils::math::{lerp, quadratic, square}; -use crate::utils::parameters::ParameterDictionary; -use std::path::Path; -use std::sync::Arc; -#[derive(Debug)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct LensElementInterface { + pub curvature_radius: Float, + pub thickness: Float, + pub eta: Float, + pub aperture_radius: Float, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExitPupilSample { + pub p_pupil: Point3f, + pub pdf: Float, +} + +const EXIT_PUPIL_SAMPLES: usize = 64; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct RealisticCamera { base: CameraBase, focus_distance: Float, set_aperture_diameter: Float, - aperture_image: Option, - element_interface: Vec, + aperture_image: *const Image, + element_interfaces: *const LensElementInterface, + n_elements: usize, physical_extent: Bounds2f, - exit_pupil_bounds: Vec, + exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], } +#[cfg(not(target_os = "cuda"))] impl RealisticCamera { pub fn new( base: CameraBase, - lens_params: Vec, + lens_params: &[Float], focus_distance: Float, set_aperture_diameter: Float, aperture_image: Option, ) -> Self { - let aspect = - base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float; - let diagonal = base.film.diagonal(); + let film_ptr = base.film; + if film_ptr.is_null() { + panic!("Camera must have a film"); + } + let film = unsafe { &*film_ptr }; + + let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float; + let diagonal = film.diagonal(); let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt(); let y = x * aspect; let physical_extent = @@ -73,19 +91,24 @@ impl RealisticCamera { } let n_samples = 64; - let half_diag = base.film.diagonal() / 2.0; - let exit_pupil_bounds: Vec<_> = (0..n_samples) - .map(|i| { - let r0 = (i as Float / n_samples as Float) * half_diag; - let r1 = ((i + 1) as Float / n_samples as Float) * half_diag; - Self::compute_exit_pupil_bounds(&element_interface, r0, r1) - }) - .collect(); + let half_diag = film.diagonal() / 2.0; + let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES]; + + for i in 0..EXIT_PUPIL_SAMPLES { + let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; + let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag; + exit_pupil_bounds[i] = Self::compute_exit_pupil_bounds(&element_interface, r0, r1); + } + + let n_elements = element_interface.len(); + let element_interfaces = element_interface.as_ptr(); + std::mem::forget(element_interface); Self { base, focus_distance, - element_interface, + element_interfaces, + n_elements, physical_extent, set_aperture_diameter, aperture_image, @@ -93,20 +116,58 @@ impl RealisticCamera { } } - pub fn lens_rear_z(&self) -> Float { - self.element_interface.last().unwrap().thickness + pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) { + let tf = -r_out.o.x() / r_out.d.x(); + let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x(); + (-r_out.at(tf).z(), -r_out.at(tp).z()) } - pub fn lens_front_z(&self) -> Float { - let mut z_sum = 0.; - for element in &self.element_interface { - z_sum += element.thickness; + pub fn compute_thick_lens_approximation(&self) -> ([Float; 2], [Float; 2]) { + let x = 0.001 * self.get_film().diagonal(); + let r_scene = Ray::new( + Point3f::new(0., x, self.lens_front_z() + 1.), + Vector3f::new(0., 0., -1.), + None, + None, + ); + let Some(r_film) = self.trace_lenses_from_film(r_scene) else { + panic!( + "Unable to trace ray from scene to film for thick lens approx. Is aperture very small?" + ) + }; + let (pz0, fz0) = Self::compute_cardinal_points(r_scene, r_film); + let r_film = Ray::new( + Point3f::new(x, 0., self.lens_rear_z() - 1.), + Vector3f::new(0., 0., 1.), + None, + None, + ); + let Some(r_scene) = self.trace_lenses_from_film(r_film) else { + panic!( + "Unable to trace ray from scene to film for thick lens approx. Is aperture very small?" + ) + }; + let (pz1, f_1) = Self::compute_cardinal_points(r_film, r_scene); + ([pz0, pz1], [fz0, fz1]) + } + + pub fn focus_thick_lens(&self, focus_distance: Float) -> Float { + let (pz, fz) = self.compute_thick_lens_approximation(); + let f = fz[0] - pz[0]; + let z = -focus_distance; + let c = (pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0]); + if c <= 0 { + panic!( + "Coefficient must be positive. It looks focusDistance {} is too short for a given lenses configuration", + focusDistance + ); } - z_sum + let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.; + self.element_interface.last().thickness + delta } - pub fn rear_element_radius(&self) -> Float { - self.element_interface.last().unwrap().aperture_radius + pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f { + Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1) } fn compute_exit_pupil_bounds( @@ -160,38 +221,40 @@ impl RealisticCamera { pupil_bounds } - pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f { - Self::compute_exit_pupil_bounds(&self.element_interface, film_x_0, film_x_1) - } + pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option { + // Find exit pupil bound for sample distance from film center + let film = self.film(); + let r_film = (square(p_film.x()) + square(p_film.y())).sqrt(); + let mut r_index = (r_film / (film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len(); + r_index = (self.exit_pupil_bounds.len() - 1).min(r_index); - pub fn intersect_spherical_element( - radius: Float, - z_center: Float, - ray: &Ray, - ) -> Option<(Float, Normal3f)> { - let o = ray.o - Vector3f::new(0.0, 0.0, z_center); - - let a = ray.d.norm_squared(); - let b = 2.0 * ray.d.dot(o.into()); - let c = Vector3f::from(o).norm_squared() - radius * radius; - - let (t0, t1) = quadratic(a, b, c)?; - - let use_closer_t = (ray.d.z() > 0.0) ^ (radius < 0.0); - let t = if use_closer_t { t0.min(t1) } else { t0.max(t1) }; - - // Intersection is behind the ray's origin. - if t < 0.0 { + let pupil_bounds = self.exit_pupil_bounds[r_index]; + if pupil_bounds.is_degenerate() { return None; } - let p_hit_relative = o + ray.d * t; - // Ensures the normal points towards the incident ray. - let n = Normal3f::from(Vector3f::from(p_hit_relative)) - .normalize() - .face_forward(-ray.d); + // Generate sample point inside exit pupil bound + let p_lens = pupil_bounds.lerp(u_lens); + let pdf = 1. / pupil_bounds.area(); - Some((t, n)) + // Return sample point rotated by angle of _pFilm_ with $+x$ axis + let sin_theta = if r_film != 0. { + p_film.y() / r_film + } else { + 0. + }; + let cos_theta = if r_film != 0. { + p_film.x() / r_film + } else { + 1. + }; + let p_pupil = Point3f::new( + cos_theta * p_lens.x() - sin_theta * p_lens.y(), + sin_theta * p_lens.x() + cos_theta * p_lens.y(), + self.lens_rear_z(), + ); + + Some(ExitPupilSample { p_pupil, pdf }) } pub fn trace_lenses_from_film(&self, r_camera: &Ray) -> Option<(Float, Ray)> { @@ -274,229 +337,72 @@ impl RealisticCamera { Some((weight, r_out)) } - pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option { - // Find exit pupil bound for sample distance from film center - let r_film = (square(p_film.x()) + square(p_film.y())).sqrt(); - let mut r_index = - (r_film / (self.base.film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len(); - r_index = (self.exit_pupil_bounds.len() - 1).min(r_index); + fn intersect_spherical_element( + radius: Float, + z_center: Float, + ray: &Ray, + ) -> Option<(Float, Normal3f)> { + let o = ray.o - Vector3f::new(0.0, 0.0, z_center); - let pupil_bounds = self.exit_pupil_bounds[r_index]; - if pupil_bounds.is_degenerate() { + let a = ray.d.norm_squared(); + let b = 2.0 * ray.d.dot(o.into()); + let c = Vector3f::from(o).norm_squared() - radius * radius; + + let (t0, t1) = quadratic(a, b, c)?; + + let use_closer_t = (ray.d.z() > 0.0) ^ (radius < 0.0); + let t = if use_closer_t { t0.min(t1) } else { t0.max(t1) }; + + // Intersection is behind the ray's origin. + if t < 0.0 { return None; } - // Generate sample point inside exit pupil bound - let p_lens = pupil_bounds.lerp(u_lens); - let pdf = 1. / pupil_bounds.area(); + let p_hit_relative = o + ray.d * t; + // Ensures the normal points towards the incident ray. + let n = Normal3f::from(Vector3f::from(p_hit_relative)) + .normalize() + .face_forward(-ray.d); - // Return sample point rotated by angle of _pFilm_ with $+x$ axis - let sin_theta = if r_film != 0. { - p_film.y() / r_film - } else { - 0. - }; - let cos_theta = if r_film != 0. { - p_film.x() / r_film - } else { - 1. - }; - let p_pupil = Point3f::new( - cos_theta * p_lens.x() - sin_theta * p_lens.y(), - sin_theta * p_lens.x() + cos_theta * p_lens.y(), - self.lens_rear_z(), - ); - - Some(ExitPupilSample { p_pupil, pdf }) + Some((t, n)) } - pub fn create( - params: &ParameterDictionary, - camera_transform: &CameraTransform, - film: Arc, - medium: Medium, - loc: &FileLoc, - ) -> Result { - let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); - let base = CameraBase::new(camera_params); - let aperture_diameter = params.get_one_float("aperturediameter", 1.); - let focal_distance = params.get_one_float("focaldistance", 10.); - let lens_file = params.get_one_string("lensfile", ""); + pub fn lens_rear_z(&self) -> Float { + self.element_interface.last().unwrap().thickness + } - if lens_file.is_empty() { - return Err(format!("{}: No lens file supplied", loc)); + pub fn lens_front_z(&self) -> Float { + let mut z_sum = 0.; + for element in &self.element_interface { + z_sum += element.thickness; } + z_sum + } - let lens_params = read_float_file(lens_file.as_str()).map_err(|e| e.to_string())?; - if lens_params.len() % 4 != 0 { - return Err(format!( - "{}: excess values in lens specification file; must be multiple-of-four values, read {}", - loc, - lens_params.len() - )); - } - - let builtin_res = 256; - let rasterize = |vert: &[Point2f]| -> Image { - let mut image = Image::new( - PixelFormat::F32, - Point2i::new(builtin_res, builtin_res), - &["Y"], - SRGB, - ); - - let res = image.resolution(); - for y in 0..res.y() { - for x in 0..res.x() { - // Map pixel to [-1, 1] range - let p = Point2f::new( - -1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float, - -1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float, - ); - - let mut winding_number = 0; - // Winding number test against edges - for i in 0..vert.len() { - let i1 = (i + 1) % vert.len(); - let v_i = vert[i]; - let v_i1 = vert[i1]; - - let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y()) - - (p.y() - v_i.y()) * (v_i1.x() - v_i.x()); - - if v_i.y() <= p.y() { - if v_i1.y() > p.y() && e > 0.0 { - winding_number += 1; - } - } else if v_i1.y() <= p.y() && e < 0.0 { - winding_number -= 1; - } - } - - image.set_channel( - Point2i::new(x, y), - 0, - if winding_number == 0 { 0.0 } else { 1.0 }, - ); - } - } - image - }; - - let aperture_name = params.get_one_string("aperture", ""); - let mut aperture_image: Option = None; - - if !aperture_name.is_empty() { - match aperture_name.as_str() { - "gaussian" => { - let mut img = Image::new( - PixelFormat::F32, - Point2i::new(builtin_res, builtin_res), - &["Y"], - SRGB, - ); - let res = img.resolution(); - for y in 0..res.y() { - for x in 0..res.x() { - let uv = Point2f::new( - -1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float, - -1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float, - ); - let r2 = square(uv.x()) + square(uv.y()); - let sigma2 = 1.0; - let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0); - img.set_channel(Point2i::new(x, y), 0, v); - } - } - aperture_image = Some(img); - } - "square" => { - let mut img = Image::new( - PixelFormat::F32, - Point2i::new(builtin_res, builtin_res), - &["Y"], - SRGB, - ); - let low = (0.25 * builtin_res as Float) as i32; - let high = (0.75 * builtin_res as Float) as i32; - for y in low..high { - for x in low..high { - img.set_channel(Point2i::new(x, y), 0, 4.0); - } - } - aperture_image = Some(img); - } - "pentagon" => { - let c1 = (5.0f32.sqrt() - 1.0) / 4.0; - let c2 = (5.0f32.sqrt() + 1.0) / 4.0; - let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0; - let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0; - let mut vert = [ - Point2f::new(0.0, 1.0), - Point2f::new(s1, c1), - Point2f::new(s2, -c2), - Point2f::new(-s2, -c2), - Point2f::new(-s1, c1), - ]; - for v in vert.iter_mut() { - *v = Point2f::from(Vector2f::from(*v) * 0.8); - } - aperture_image = Some(rasterize(&vert)); - } - "star" => { - let mut vert = Vec::with_capacity(10); - for i in 0..10 { - let r = if i % 2 == 1 { - 1.0 - } else { - (72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos()) - }; - let angle = PI * i as Float / 5.0; - vert.push(Point2f::new(r * angle.cos(), r * angle.sin())); - } - vert.reverse(); - aperture_image = Some(rasterize(&vert)); - } - _ => { - if let Ok(im) = Image::read(Path::new(&aperture_name), None) { - if im.image.n_channels() > 1 { - let mut mono = - Image::new(PixelFormat::F32, im.image.resolution(), &["Y"], SRGB); - let res = mono.resolution(); - for y in 0..res.y() { - for x in 0..res.x() { - let avg = - im.image.get_channels_default(Point2i::new(x, y)).average(); - mono.set_channel(Point2i::new(x, y), 0, avg); - } - } - aperture_image = Some(mono); - } else { - aperture_image = Some(im.image); - } - } - } - } - } - - Ok(Self::new( - base, - lens_params, - focal_distance, - aperture_diameter, - aperture_image, - )) + pub fn rear_element_radius(&self) -> Float { + self.element_interface.last().unwrap().aperture_radius } } impl CameraTrait for RealisticCamera { + fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { + self.base.init_metadata(metadata) + } + fn base(&self) -> &CameraBase { &self.base } - fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { - self.base.init_metadata(metadata) + fn get_film(&self) -> &Film { + #[cfg(not(target_os = "cuda"))] + { + if self.base.film.is_null() { + panic!( + "FilmBase error: PixelSensor pointer is null. This should have been checked during construction." + ); + } + } + unsafe { &*self.base.film } } fn generate_ray( @@ -505,9 +411,10 @@ impl CameraTrait for RealisticCamera { _lambda: &SampledWavelengths, ) -> Option { // Find point on film, _pFilm_, corresponding to _sample.pFilm_ + let film = self.get_film(); let s = Point2f::new( - sample.p_film.x() / self.base.film.full_resolution().x() as Float, - sample.p_film.y() / self.base.film.full_resolution().y() as Float, + sample.p_film.x() / film.full_resolution().x() as Float, + sample.p_film.y() / film.full_resolution().y() as Float, ); let p_film2 = self.physical_extent.lerp(s); let p_film = Point3f::new(-p_film2.x(), p_film2.y(), 0.); diff --git a/shared/src/cameras/spherical.rs b/shared/src/cameras/spherical.rs index 65cd225..1efca11 100644 --- a/shared/src/cameras/spherical.rs +++ b/shared/src/cameras/spherical.rs @@ -1,91 +1,30 @@ -use super::{CameraBase, CameraBaseParameters, CameraRay, CameraTrait, CameraTransform}; -use crate::core::film::{Film, FilmTrait}; +use crate::core::camera::{CameraBase, CameraRay, CameraTransform}; +use crate::core::film::Film; use crate::core::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction}; use crate::core::medium::Medium; -use crate::core::options::get_options; use crate::core::pbrt::{Float, PI}; use crate::core::sampler::CameraSample; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::error::FileLoc; use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square}; -use crate::utils::parameters::ParameterDictionary; use std::sync::Arc; -#[derive(Debug, PartialEq)] +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum Mapping { EquiRectangular, EqualArea, } -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct SphericalCamera { - pub base: CameraBase, - pub screen: Bounds2f, - pub lens_radius: Float, - pub focal_distance: Float, pub mapping: Mapping, + pub base: CameraBase, } +#[cfg(not(target_os = "cuda"))] impl SphericalCamera { - pub fn create( - params: &ParameterDictionary, - camera_transform: &CameraTransform, - film: Arc, - medium: Medium, - loc: &FileLoc, - ) -> Result { - let full_res = film.full_resolution(); - let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc); - let base = CameraBase::new(camera_params); - let lens_radius = params.get_one_float("lensradius", 0.); - let focal_distance = params.get_one_float("focaldistance", 1e30); - let frame = params.get_one_float( - "frameaspectratio", - full_res.x() as Float / full_res.y() as Float, - ); - - let mut screen = if frame > 1. { - Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) - } else { - Bounds2f::from_points(Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame)) - }; - - let sw = params.get_float_array("screenwindow"); - if !sw.is_empty() { - if get_options().fullscreen { - eprint!("Screenwindow is ignored in fullscreen mode"); - } else { - if sw.len() == 4 { - screen = Bounds2f::from_points( - Point2f::new(sw[0], sw[2]), - Point2f::new(sw[1], sw[3]), - ); - } else { - return Err(format!("{}: screenwindow param must have four values", loc)); - } - } - } - - let m = params.get_one_string("mapping", "equalarea"); - let mapping = match m.as_str() { - "equal_area" => Mapping::EqualArea, - "equirectangular" => Mapping::EquiRectangular, - _ => { - return Err(format!( - "{}: unknown mapping for spherical camera at {}", - m, loc - )); - } - }; - - Ok(Self { - mapping, - base, - screen, - lens_radius, - focal_distance, - }) + pub fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { + self.base.init_metadata(metadata) } } @@ -94,8 +33,16 @@ impl CameraTrait for SphericalCamera { &self.base } - fn init_metadata(&self, metadata: &mut crate::image::ImageMetadata) { - self.base.init_metadata(metadata) + fn get_film(&self) -> &Film { + #[cfg(not(target_os = "cuda"))] + { + if self.base.film.is_null() { + panic!( + "FilmBase error: PixelSensor pointer is null. This should have been checked during construction." + ); + } + } + unsafe { &*self.base.film } } fn generate_ray( @@ -104,9 +51,10 @@ impl CameraTrait for SphericalCamera { _lamdba: &SampledWavelengths, ) -> Option { // Compute spherical camera ray direction + let film = self.get_film(); let mut uv = Point2f::new( - sample.p_film.x() / self.base().film.full_resolution().x() as Float, - sample.p_film.y() / self.base().film.full_resolution().y() as Float, + sample.p_film.x() / film.full_resolution().x() as Float, + sample.p_film.y() / film.full_resolution().y() as Float, ); let dir: Vector3f; if self.mapping == Mapping::EquiRectangular { diff --git a/shared/src/core/aggregates.rs b/shared/src/core/aggregates.rs index 5844622..0f8642f 100644 --- a/shared/src/core/aggregates.rs +++ b/shared/src/core/aggregates.rs @@ -10,6 +10,7 @@ use std::cmp::Ordering; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +#[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SplitMethod { SAH, @@ -18,10 +19,11 @@ pub enum SplitMethod { EqualCounts, } +#[repr(C)] #[derive(Debug, Default, Clone, Copy, PartialEq)] struct BVHSplitBucket { - count: usize, - bounds: Bounds3f, + pub count: usize, + pub bounds: Bounds3f, } #[derive(Debug, Clone, Default)] diff --git a/shared/src/core/camera.rs b/shared/src/core/camera.rs index a31b75d..dd11d54 100644 --- a/shared/src/core/camera.rs +++ b/shared/src/core/camera.rs @@ -1,4 +1,5 @@ -use crate::core::film::{Film, FilmTrait}; +use crate::cameras::*; +use crate::core::film::Film; use crate::core::geometry::{ Normal3f, Point2f, Point2i, Point3f, Ray, RayDifferential, Vector3f, VectorLike, }; @@ -9,34 +10,31 @@ use crate::core::pbrt::Float; use crate::core::sampler::CameraSample; use crate::images::ImageMetadata; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::error::FileLoc; use crate::utils::math::lerp; -use crate::utils::parameters::ParameterDictionary; use crate::utils::transform::{AnimatedTransform, Transform}; use enum_dispatch::enum_dispatch; -use std::sync::Arc; - #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct CameraRay { pub ray: Ray, pub weight: SampledSpectrum, } #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct CameraWiSample { - wi_spec: SampledSpectrum, - wi: Vector3f, - pdf: Float, - p_raster: Point2f, - p_ref: Interaction, - p_lens: Interaction, + pub wi_spec: SampledSpectrum, + pub wi: Vector3f, + pub pdf: Float, + pub p_raster: Point2f, + pub p_ref: Interaction, + pub p_lens: Interaction, } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct CameraTransform { pub render_from_camera: AnimatedTransform, pub world_from_render: Transform, @@ -106,55 +104,20 @@ impl CameraTransform { } #[repr(C)] -#[derive(Clone, Debug)] -pub struct CameraBaseParameters { - pub camera_transform: CameraTransform, - pub shutter_open: Float, - pub shutter_close: Float, - pub film: Arc, - pub medium_id: i32, -} - -impl CameraBaseParameters { - pub fn new( - camera_transform: &CameraTransform, - film: Arc, - medium: Arc, - params: &ParameterDictionary, - loc: &FileLoc, - ) -> Self { - let mut shutter_open = params.get_one_float("shutteropen", 0.); - let mut shutter_close = params.get_one_float("shutterclose", 1.); - if shutter_close < shutter_open { - eprint!( - "{}: Shutter close time {} < shutter open {}. Swapping", - loc, shutter_close, shutter_open - ); - std::mem::swap(&mut shutter_open, &mut shutter_close); - } - CameraBaseParameters { - camera_transform: Arc::new(camera_transform.clone()), - shutter_open, - shutter_close, - film, - medium_id: 0, - } - } -} - -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct CameraBase { pub camera_transform: CameraTransform, pub shutter_open: Float, pub shutter_close: Float, - pub film: Arc, - pub medium: Option>, + pub film: *const Film, + pub medium: *const Medium, pub min_pos_differential_x: Vector3f, pub min_pos_differential_y: Vector3f, pub min_dir_differential_x: Vector3f, pub min_dir_differential_y: Vector3f, } +#[cfg(not(target_os = "cuda"))] impl CameraBase { pub fn init_metadata(&self, metadata: &mut ImageMetadata) { let camera_from_world: Transform = @@ -162,83 +125,97 @@ impl CameraBase { metadata.camera_from_world = Some(camera_from_world.get_matrix()); } +} - pub fn new(p: CameraBaseParameters) -> Self { - Self { - camera_transform: p.camera_transform.as_ref().clone(), - shutter_open: p.shutter_open, - shutter_close: p.shutter_close, - film: p.film.clone(), - medium: p.medium, - min_pos_differential_x: Vector3f::default(), - min_pos_differential_y: Vector3f::default(), - min_dir_differential_x: Vector3f::default(), - min_dir_differential_y: Vector3f::default(), - } - } +#[enum_dispatch(CameraTrait)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub enum Camera { + Perspective(PerspectiveCamera), + Orthographic(OrthographicCamera), + Spherical(SphericalCamera), + Realistic(RealisticCamera), } #[enum_dispatch] pub trait CameraTrait { + #[cfg(not(target_os = "cuda"))] + fn init_metadata(&self, metadata: &mut ImageMetadata); + fn base(&self) -> &CameraBase; - fn get_film(&self) -> &Film { - &self.base().film + + fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option; + + #[cfg(not(target_os = "cuda"))] + fn get_film(&self) -> Result<&Film, String> { + if self.film.is_null() { + return Err("Camera error: Film pointer is null.".to_string()); + } + Ok(unsafe { &*self.film }) + } + + fn sample_time(&self, u: Float) -> Float { + lerp(u, self.base().shutter_open, self.base().shutter_close) } fn resolution(&self) -> Point2i { self.base().film.full_resolution() } - fn init_metadata(&self, metadata: &mut ImageMetadata); + fn render_from_camera(&self, r: &Ray, t_max: &mut Option) -> Ray { + self.base() + .camera_transform + .render_from_camera_ray(r, t_max) + } - fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option; fn generate_ray_differential( &self, sample: CameraSample, lambda: &SampledWavelengths, ) -> Option { - let mut central_cam_ray = self.generate_ray(sample, lambda)?; - let mut rd = RayDifferential::default(); - let mut rx_found = false; - let mut ry_found = false; + match self { + Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda), + _ => { + let mut central_cam_ray = self.generate_ray(sample, lambda)?; + let mut rd = RayDifferential::default(); + let mut rx_found = false; + let mut ry_found = false; - for eps in [0.05, -0.05] { - let mut s_shift = sample; - s_shift.p_film[0] += eps; + for eps in [0.05, -0.05] { + let mut s_shift = sample; + s_shift.p_film[0] += eps; - if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) { - rd.rx_origin = - central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps; - rd.rx_direction = - central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps; - rx_found = true; - break; + if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) { + rd.rx_origin = central_cam_ray.ray.o + + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps; + rd.rx_direction = central_cam_ray.ray.d + + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps; + rx_found = true; + break; + } + } + + for eps in [0.05, -0.05] { + let mut s_shift = sample; + s_shift.p_film[1] += eps; + + if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) { + rd.ry_origin = central_cam_ray.ray.o + + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps; + rd.ry_direction = central_cam_ray.ray.d + + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps; + ry_found = true; + break; + } + } + + if rx_found && ry_found { + central_cam_ray.ray.differential = Some(rd); + } + + Some(central_cam_ray) } } - - for eps in [0.05, -0.05] { - let mut s_shift = sample; - s_shift.p_film[1] += eps; - - if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) { - rd.ry_origin = - central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps; - rd.ry_direction = - central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps; - ry_found = true; - break; - } - } - - if rx_found && ry_found { - central_cam_ray.ray.differential = Some(rd); - } - - Some(central_cam_ray) - } - - fn sample_time(&self, u: Float) -> Float { - lerp(u, self.base().shutter_open, self.base().shutter_close) } fn approximate_dp_dxy( @@ -290,65 +267,4 @@ pub trait CameraTrait { time, ); } - - fn render_from_camera(&self, r: &Ray, t_max: &mut Option) -> 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, - loc: &FileLoc, - ) -> Result { - 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, } diff --git a/shared/src/spectra/color.rs b/shared/src/core/color.rs similarity index 79% rename from shared/src/spectra/color.rs rename to shared/src/core/color.rs index 4205c20..61c610c 100644 --- a/shared/src/spectra/color.rs +++ b/shared/src/core/color.rs @@ -5,17 +5,12 @@ use std::ops::{ }; use crate::core::geometry::Point2f; -use crate::core::pbrt::Float; -use crate::spectra::Spectrum; -use crate::utils::math::{SquareMatrix, evaluate_polynomial, lerp}; -use once_cell::sync::Lazy; +use crate::core::pbrt::{Float, find_interval}; +use crate::core::spectrum::Spectrum; +use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp}; use enum_dispatch::enum_dispatch; -pub trait Triplet { - fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self; -} - #[derive(Debug, Clone)] pub struct XYZ { pub x: Float, @@ -23,9 +18,9 @@ pub struct XYZ { pub z: Float, } -impl Triplet for XYZ { - fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self { - XYZ::new(c1, c2, c3) +impl From<(Float, Float, Float)> for XYZ { + fn from(triplet: (Float, Float, Float)) -> Self { + XYZ::new(triplet.0, triplet.1, triplet.2) } } @@ -259,9 +254,9 @@ pub struct RGB { pub b: Float, } -impl Triplet for RGB { - fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self { - RGB::new(c1, c2, c3) +impl From<(Float, Float, Float)> for RGB { + fn from(triplet: (Float, Float, Float)) -> Self { + RGB::new(triplet.0, triplet.1, triplet.2) } } @@ -467,7 +462,7 @@ impl fmt::Display for RGB { } } -impl Mul for SquareMatrix { +impl Mul for SquareMatrix3f { type Output = RGB; fn mul(self, v: XYZ) -> RGB { @@ -478,7 +473,7 @@ impl Mul for SquareMatrix { } } -impl Mul for SquareMatrix { +impl Mul for SquareMatrix3f { type Output = XYZ; fn mul(self, v: RGB) -> XYZ { let x = self[0][0] * v.r + self[0][1] * v.g + self[0][2] * v.b; @@ -493,7 +488,7 @@ pub trait MatrixMulColor { fn mul_xyz(&self, v: XYZ) -> XYZ; } -impl MatrixMulColor for SquareMatrix { +impl MatrixMulColor for SquareMatrix3f { fn mul_rgb(&self, v: RGB) -> RGB { let m = self; RGB::new( @@ -513,65 +508,22 @@ impl MatrixMulColor for SquareMatrix { } } -pub const RES: usize = 64; -pub type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3]; - -#[derive(Debug)] -pub struct RGBToSpectrumTable { - coeffs: &'static [Float], - scale: &'static [Float], -} - -impl RGBToSpectrumTable { - pub fn srgb() -> Self { - Self { - coeffs: *crate::data::SRGB_COEFFS, - scale: *crate::data::SRGB_SCALE, - } - } - - pub fn dci_p3() -> Self { - Self { - coeffs: *crate::data::DCI_P3_COEFFS, - scale: *crate::data::DCI_P3_SCALE, - } - } - - pub fn rec2020() -> Self { - Self { - coeffs: *crate::data::REC2020_COEFFS, - scale: *crate::data::REC2020_SCALE, - } - } - - pub fn aces2065_1() -> Self { - Self { - coeffs: *crate::data::ACES_COEFFS, - scale: *crate::data::ACES_SCALE, - } - } -} - +#[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct RGBSigmoidPolynomial { - c0: Float, - c1: Float, - c2: Float, + pub c0: Float, + pub c1: Float, + pub c2: Float, } impl RGBSigmoidPolynomial { + #[cfg(not(target_os = "cuda"))] pub fn new(c0: Float, c1: Float, c2: Float) -> Self { Self { c0, c1, c2 } } pub fn evaluate(&self, lambda: Float) -> Float { - let eval = match evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) { - Some(value) => value, - None => { - panic!("evaluate_polynomial returned None with non-empty coefficients") - } - }; - + let eval = evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]); Self::s(eval) } @@ -586,120 +538,16 @@ impl RGBSigmoidPolynomial { fn s(x: Float) -> Float { if x.is_infinite() { - if x > 0.0 { return 1.0 } else { return 0.0 } + if x > 0.0 { + return 1.0; + } else { + return 0.0; + } } 0.5 + x / (2.0 * (1.0 + (x * x)).sqrt()) } } -impl RGBToSpectrumTable { - pub fn new(scale: &'static [Float], coeffs: &'static [Float]) -> Self { - Self { scale, coeffs } - } - - pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial { - //TODO: Check all of this logic, this is important - if rgb[0] == rgb[1] && rgb[1] == rgb[2] { - return RGBSigmoidPolynomial::new( - 0.0, - 0.0, - (rgb[0] - 0.5) / (rgb[0] * (1.0 - rgb[0])).sqrt(), - ); - } - - let max_c = rgb.max_component_value(); - let c = [rgb.r, rgb.g, rgb.b]; - let (min_c, mid_c) = if c[0] < c[1] { - if c[1] < c[2] { - (c[0], c[1]) - } - // 0 < 1 < 2 - else if c[0] < c[2] { - (c[0], c[2]) - } - // 0 < 2 < 1 - else { - (c[2], c[0]) - } // 2 < 0 < 1 - } else { - if c[1] > c[2] { - (c[2], c[1]) - } - // 2 < 1 < 0 - else if c[0] > c[2] { - (c[1], c[2]) - } - // 1 < 2 < 0 - else { - (c[1], c[0]) - } // 1 < 0 < 2 - }; - - let z = min_c / max_c; - let x = mid_c / max_c; - - let z_float = z * (RES - 1) as Float; - let zi = (z_float as usize).min(RES - 2); - let z_t = z_float - zi as Float; - let x_float = x * (RES - 1) as Float; - let xi = (x_float as usize).min(RES - 2); - let x_t = x_float - xi as Float; - - let mut coeffs = [0.0; 3]; - #[allow(clippy::needless_range_loop)] - for i in 0..3 { - let offset = |dz, dx| (((zi + dz) * RES + (xi + dx)) * RES + (RES - 1)) * 3 + i; - let c00 = self.coeffs[offset(0, 0)]; - let c01 = self.coeffs[offset(0, 1)]; - let c10 = self.coeffs[offset(1, 0)]; - let c11 = self.coeffs[offset(1, 1)]; - let v0 = lerp(x_t, c00, c01); - let v1 = lerp(x_t, c10, c11); - coeffs[i] = lerp(z_t, v0, v1); - } - - let scale_float = max_c * (RES - 1) as Float; - let si = (scale_float as usize).min(RES - 2); - let s_t = scale_float - si as Float; - - let scale = lerp(s_t, self.scale[si], self.scale[si + 1]); - - RGBSigmoidPolynomial { - c0: coeffs[0], - c1: coeffs[1], - c2: coeffs[2], - } - } -} - -const LMS_FROM_XYZ: SquareMatrix = 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 = 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 { - // 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::::diag(&[ - dst_lms[0] / src_lms[0], - dst_lms[1] / src_lms[1], - dst_lms[2] / src_lms[2], - ]); - XYZ_FROM_LMS * lms_correct * LMS_FROM_XYZ -} - #[enum_dispatch] pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display { fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]); @@ -710,6 +558,7 @@ pub trait ColorEncodingTrait: 'static + Send + Sync + fmt::Debug + fmt::Display } } +#[repr(C)] #[enum_dispatch(ColorEncodingTrait)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum ColorEncoding { @@ -723,6 +572,7 @@ impl fmt::Display for ColorEncoding { } } +#[repr(C)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct LinearEncoding; impl ColorEncodingTrait for LinearEncoding { @@ -747,6 +597,7 @@ impl fmt::Display for LinearEncoding { } } +#[repr(C)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct SRGBEncoding; impl ColorEncodingTrait for SRGBEncoding { @@ -1045,3 +896,122 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [ 0.9911022186, 1.0000000000, ]; + +pub const RES: usize = 64; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +pub struct Coeffs { + pub c0: Float, + pub c1: Float, + pub c2: Float, +} + +impl Add for Coeffs { + type Output = Self; + #[inline(always)] + fn add(self, rhs: Self) -> Self { + Self { + c0: self.c0 + rhs.c0, + c1: self.c1 + rhs.c1, + c2: self.c2 + rhs.c2, + } + } +} + +impl Mul 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, + } + } +} diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index 424f346..0dc92b0 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -1,4 +1,5 @@ use crate::core::camera::CameraTransform; +use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance}; use crate::core::filter::Filter; use crate::core::geometry::{ Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, @@ -6,13 +7,12 @@ use crate::core::geometry::{ }; use crate::core::interaction::SurfaceInteraction; use crate::core::pbrt::Float; +use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra}; use crate::images::{Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat}; -use crate::spectra::color::{MatrixMulColor, SRGB, Triplet, white_balance}; -use crate::spectra::data::generate_cie_d; use crate::spectra::{ ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, - PiecewiseLinearSpectrum, RGB, RGBColorSpace, SampledSpectrum, SampledWavelengths, Spectrum, - SpectrumTrait, XYZ, cie_x, cie_y, cie_z, colorspace, get_named_spectrum, + PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace, + get_named_spectrum, }; use crate::utils::AtomicFloat; use crate::utils::containers::Array2D; @@ -20,7 +20,6 @@ use crate::utils::math::linear_least_squares; use crate::utils::math::{SquareMatrix, wrap_equal_area_square}; use crate::utils::sampling::VarianceEstimator; use crate::utils::transform::AnimatedTransform; -use std::sync::Arc; #[repr(C)] #[derive(Clone, Copy, Debug)] @@ -30,7 +29,7 @@ pub struct RGBFilm { pub write_fp16: bool, pub filter_integral: Float, pub output_rgbf_from_sensor_rgb: SquareMatrix, - pub pixels: Arc>, + pub pixels: Array2D, } #[repr(C)] @@ -53,7 +52,7 @@ impl RGBFilm { if sensor_ptr.is_null() { panic!("Film must have a sensor"); } - let sensor = unsafe { &*self.sensor }; + let sensor = unsafe { &*sensor_ptr }; let filter_integral = base.filter.integral(); let sensor_matrix = sensor.xyz_from_sensor_rgb; let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix; @@ -75,7 +74,7 @@ impl RGBFilm { write_fp16, filter_integral, output_rgbf_from_sensor_rgb, - pixels: Arc::new(pixels_array), + pixels: std::sync::Arc::new(pixels_array), } } } @@ -89,12 +88,16 @@ impl RGBFilm { &mut self.base } - #[cfg(not(target_os = "cuda"))] - pub fn get_sensor(&self) -> Result<&PixelSensor, String> { - if self.sensor.is_null() { - return Err("FilmBase error: PixelSensor pointer is null.".to_string()); + pub fn get_sensor(&self) -> &PixelSensor { + #[cfg(not(target_os = "cuda"))] + { + if self.sensor.is_null() { + panic!( + "FilmBase error: PixelSensor pointer is null. This should have been checked during construction." + ); + } } - Ok(unsafe { &*self.sensor }) + unsafe { &*self.sensor } } pub fn add_sample( @@ -259,27 +262,20 @@ impl GBufferFilm { &mut self.base } - #[cfg(not(target_os = "cuda"))] - pub fn get_sensor(&self) -> Result<&PixelSensor, String> { - if self.sensor.is_null() { - return Err("FilmBase error: PixelSensor pointer is null.".to_string()); - } - Ok(unsafe { &*self.sensor }) - } - - pub unsafe fn get_sensor(&self) -> &PixelSensor { + pub fn get_sensor(&self) -> &PixelSensor { #[cfg(not(target_os = "cuda"))] { if self.sensor.is_null() { - panic!("FilmBase: PixelSensor pointer is null"); + panic!( + "FilmBase error: PixelSensor pointer is null. This should have been checked during construction." + ); } } - - &*self.sensor + unsafe { &*self.sensor } } pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { - let sensor = unsafe { self.get_pixel_sensor() }; + let sensor = unsafe { self.get_sensor() }; let mut rgb = sensor.to_sensor_rgb(l, lambda); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); if m > self.max_component_value { @@ -310,7 +306,7 @@ impl GBufferFilm { } pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { - let sensor = unsafe { self.get_pixel_sensor() }; + let sensor = unsafe { self.get_sensor() }; let sensor_rgb = sensor.to_sensor_rgb(l, lambda); self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) } @@ -467,15 +463,15 @@ pub struct PixelSensor { pub imaging_ratio: Float, } -#[cfg(not(target_os = "cuda"))] impl PixelSensor { const N_SWATCH_REFLECTANCES: usize = 24; + #[cfg(not(target_os = "cuda"))] pub fn new( r: Spectrum, g: Spectrum, b: Spectrum, output_colorspace: RGBColorSpace, - sensor_illum: Option>, + sensor_illum: Option>, imaging_ratio: Float, swatches: &[Spectrum; 24], ) -> Result> { @@ -490,11 +486,11 @@ impl PixelSensor { let r_bar = DenselySampledSpectrum::from_spectrum(&r); let g_bar = DenselySampledSpectrum::from_spectrum(&g); let b_bar = DenselySampledSpectrum::from_spectrum(&b); - let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES]; + let mut rgb_camera = [[0.; 3]; Self::N_SWATCH_REFLECTANCES]; let swatches = Self::get_swatches(); - for i in 0..N_SWATCH_REFLECTANCES { + for i in 0..Self::N_SWATCH_REFLECTANCES { let rgb = Self::project_reflectance::( &swatches[i], illum, @@ -507,10 +503,10 @@ impl PixelSensor { } } - let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES]; + let mut xyz_output = [[0.; 3]; Self::N_SWATCH_REFLECTANCES]; let sensor_white_g = illum.inner_product(&Spectrum::DenselySampled(g_bar.clone())); let sensor_white_y = illum.inner_product(cie_y()); - for i in 0..N_SWATCH_REFLECTANCES { + for i in 0..Self::N_SWATCH_REFLECTANCES { let s = swatches[i].clone(); let xyz = Self::project_reflectance::( &s, @@ -537,7 +533,7 @@ impl PixelSensor { pub fn new_with_white_balance( output_colorspace: &RGBColorSpace, - sensor_illum: Option>, + sensor_illum: Option>, imaging_ratio: Float, ) -> Self { let r_bar = DenselySampledSpectrum::from_spectrum(cie_x()); @@ -562,13 +558,16 @@ impl PixelSensor { } } - pub fn project_reflectance( + pub fn project_reflectance( refl: &Spectrum, illum: &Spectrum, b1: &Spectrum, b2: &Spectrum, b3: &Spectrum, - ) -> T { + ) -> T + where + T: From<[Float; 3]>, + { let mut result = [0.; 3]; let mut g_integral = 0.; @@ -590,7 +589,7 @@ impl PixelSensor { result[2] *= inv_g; } - T::from_triplet(result[0], result[1], result[2]) + T::from((result[0], result[1], result[2])) } } @@ -652,7 +651,7 @@ pub enum Film { } impl Film { - fn base(&self) -> &FilmBase { + pub fn base(&self) -> &FilmBase { match self { Film::RGB(f) => f.base(), Film::GBuffer(f) => f.base(), @@ -660,7 +659,7 @@ impl Film { } } - pub fn base(&mut self) -> &mut FilmBase { + pub fn base_mut(&mut self) -> &mut FilmBase { match self { Film::RGB(f) => f.base_mut(), Film::GBuffer(f) => f.base_mut(), diff --git a/shared/src/core/filter.rs b/shared/src/core/filter.rs index b92e608..46ffb08 100644 --- a/shared/src/core/filter.rs +++ b/shared/src/core/filter.rs @@ -1,5 +1,6 @@ use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f}; use crate::core::pbrt::Float; +use crate::filters::*; use crate::utils::containers::Array2D; use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc}; use crate::utils::sampling::PiecewiseConstant2D; @@ -12,13 +13,13 @@ pub struct FilterSample { #[derive(Clone, Debug)] pub struct FilterSampler { - domain: Bounds2f, - distrib: PiecewiseConstant2D, - f: Array2D, + pub domain: Bounds2f, + pub distrib: PiecewiseConstant2D, + pub f: Array2D, } -#[cfg(not(target_os = "cuda"))] impl FilterSampler { + #[cfg(not(target_os = "cuda"))] pub fn new(radius: Vector2f, func: F) -> Self where F: Fn(Point2f) -> Float, @@ -44,9 +45,7 @@ impl FilterSampler { let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain); Self { domain, f, distrib } } -} -impl FilterSampler { pub fn sample(&self, u: Point2f) -> FilterSample { let (p, pdf, pi) = self.distrib.sample(u); @@ -59,6 +58,15 @@ impl FilterSampler { } } +pub trait FilterTrait { + fn radius(&self) -> Vector2f; + fn evaluate(&self, p: Point2f) -> Float; + fn integral(&self) -> Float; + fn sample(&self, u: Point2f) -> FilterSample; +} + +#[repr(C)] +#[enum_dispatch(FilterTrait)] #[derive(Clone, Debug)] pub enum Filter { Box(BoxFilter), @@ -67,44 +75,3 @@ pub enum Filter { LanczosSinc(LanczosSincFilter), Triangle(TriangleFilter), } - -impl Filter { - pub fn radius(&self) -> Vector2f { - match self { - Filter::Box(f) => f.radius(), - Filter::Gaussian(f) => f.radius(), - Filter::Mitchell(f) => f.radius(), - Filter::LanczosSinc(f) => f.radius(), - Filter::Triangle(f) => f.radius(), - } - } - - pub fn evaluate(&self, p: Point2f) -> Float { - match self { - Filter::Box(f) => f.evaluate(p), - Filter::Gaussian(f) => f.evaluate(p), - Filter::Mitchell(f) => f.evaluate(p), - Filter::LanczosSinc(f) => f.evaluate(p), - Filter::Triangle(f) => f.evaluate(p), - } - } - - pub fn integral(&self) -> Float { - match self { - Filter::Box(f) => f.integral(), - Filter::Gaussian(f) => f.integral(), - Filter::Mitchell(f) => f.integral(), - Filter::LanczosSinc(f) => f.integral(), - Filter::Triangle(f) => f.integral(), - } - } - pub fn sample(&self, u: Point2f) -> FilterSample { - match self { - Filter::Box(f) => f.sample(u), - Filter::Gaussian(f) => f.sample(u), - Filter::Mitchell(f) => f.sample(u), - Filter::LanczosSinc(f) => f.sample(u), - Filter::Triangle(f) => f.sample(u), - } - } -} diff --git a/shared/src/core/geometry/ray.rs b/shared/src/core/geometry/ray.rs index d6780c8..ebeb451 100644 --- a/shared/src/core/geometry/ray.rs +++ b/shared/src/core/geometry/ray.rs @@ -2,16 +2,16 @@ use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike}; use crate::core::medium::Medium; use crate::core::pbrt::Float; use crate::utils::math::{next_float_down, next_float_up}; -use std::sync::Arc; -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct Ray { pub o: Point3f, pub d: Vector3f, - pub medium: Option>, + pub medium: *const Medium, pub time: Float, // We do this instead of creating a trait for Rayable or some gnarly thing like that - pub differential: Option, + pub differential: *const RayDifferential, } impl Default for Ray { @@ -27,7 +27,7 @@ impl Default for Ray { } impl Ray { - pub fn new(o: Point3f, d: Vector3f, time: Option, medium: Option>) -> Self { + pub fn new(o: Point3f, d: Vector3f, time: Option, medium: *const Medium) -> Self { Self { o, d, @@ -110,6 +110,7 @@ impl Ray { } } +#[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct RayDifferential { pub rx_origin: Point3f, diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 93c9dca..6a7a87b 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -1,9 +1,10 @@ -use crate::camera::{Camera, CameraTrait}; use crate::core::bssrdf::BSSRDF; use crate::core::bxdf::{BSDF, BxDF, BxDFFlags, DiffuseBxDF}; +use crate::core::camera::Camera; use crate::core::geometry::{ Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, }; +use crate::core::light::Light; use crate::core::material::{ Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, }; @@ -11,26 +12,24 @@ use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; use crate::core::options::get_options; use crate::core::pbrt::Float; use crate::core::sampler::{Sampler, SamplerTrait}; -use crate::core::texture::{FloatTexture, UniversalTextureEvaluator}; -use crate::image::Image; -use crate::lights::{Light, LightTrait}; +use crate::core::texture::{GPUFloatTexture, UniversalTextureEvaluator}; +use crate::images::Image; use crate::shapes::Shape; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::math::{clamp, difference_of_products, square}; -use bumpalo::Bump; use enum_dispatch::enum_dispatch; use std::any::Any; -use std::sync::Arc; -#[derive(Default, Clone, Debug)] +#[repr(C)] +#[derive(Default, Copy, Clone, Debug)] pub struct InteractionData { pub pi: Point3fi, pub n: Normal3f, pub time: Float, pub wo: Vector3f, - pub medium_interface: Option, - pub medium: Option>, + pub medium_interface: MediumInterface, + pub medium: *const Medium, } #[enum_dispatch] @@ -63,16 +62,16 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug { false } - fn get_medium(&self, w: Vector3f) -> Option> { + fn get_medium(&self, w: Vector3f) -> *const Medium { let data = self.get_common(); if let Some(mi) = &data.medium_interface { if w.dot(data.n.into()) > 0.0 { - mi.outside.clone() + mi.outside } else { - mi.inside.clone() + mi.inside } } else { - data.medium.clone() + data.medium } } @@ -91,9 +90,8 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug { ray } - fn spawn_ray_to_interaction(&self, other: &dyn InteractionTrait) -> Ray { + fn spawn_ray_to_interaction(&self, other: InteractionData) -> Ray { let data = self.get_common(); - let other_data = other.get_common(); let mut ray = Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n); @@ -110,8 +108,9 @@ pub trait InteractionTrait: Send + Sync + std::fmt::Debug { } } +#[repr(C)] #[enum_dispatch(InteractionTrait)] -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub enum Interaction { Surface(SurfaceInteraction), Medium(MediumInteraction), @@ -128,6 +127,7 @@ impl Interaction { } } +#[repr(C)] #[derive(Debug, Clone)] pub struct SimpleInteraction { pub common: InteractionData, @@ -171,8 +171,9 @@ impl InteractionTrait for SimpleInteraction { } } -#[derive(Default, Clone, Debug)] -pub struct Shadinggeom { +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +pub struct ShadingGeom { pub n: Normal3f, pub dpdu: Vector3f, pub dpdv: Vector3f, @@ -180,7 +181,8 @@ pub struct Shadinggeom { pub dndv: Normal3f, } -#[derive(Debug, Default, Clone)] +#[repr(C)] +#[derive(Debug, Default, Clone, Copy)] pub struct SurfaceInteraction { pub common: InteractionData, pub uv: Point2f, @@ -188,19 +190,22 @@ pub struct SurfaceInteraction { pub dpdv: Vector3f, pub dndu: Normal3f, pub dndv: Normal3f, - pub shading: Shadinggeom, - pub face_index: usize, - pub area_light: Option>, - pub material: Option>, + pub shading: ShadingGeom, + pub face_index: u32, + pub area_light: *const Light, + pub material: *const Material, + pub shape: *const Shape, pub dpdx: Vector3f, pub dpdy: Vector3f, pub dudx: Float, pub dvdx: Float, pub dudy: Float, pub dvdy: Float, - pub shape: Arc, } +unsafe impl Send for SurfaceInteraction {} +unsafe impl Sync for SurfaceInteraction {} + impl SurfaceInteraction { pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { if let Some(area_light) = &self.area_light { @@ -304,6 +309,7 @@ impl SurfaceInteraction { } } + #[cfg(not(target_os = "cuda"))] pub fn get_bsdf( &mut self, r: &Ray, @@ -343,12 +349,12 @@ impl SurfaceInteraction { Some(bsdf) } + #[cfg(not(target_os = "cuda"))] pub fn get_bssrdf( &self, _ray: &Ray, lambda: &SampledWavelengths, _camera: &Camera, - _scratch: &Bump, ) -> Option { let material = { let root_mat = self.material.as_deref()?; @@ -370,8 +376,8 @@ impl SurfaceInteraction { fn compute_bump_geom( &mut self, tex_eval: &UniversalTextureEvaluator, - displacement: Option, - normal_image: Option>, + displacement: *const GPUFloatTexture, + normal_image: *const Image, ) { let ctx = NormalBumpEvalContext::from(&*self); let (dpdu, dpdv) = if let Some(disp) = displacement { @@ -494,12 +500,12 @@ impl InteractionTrait for SurfaceInteraction { &mut self.common } - fn get_medium(&self, w: Vector3f) -> Option> { + fn get_medium(&self, w: Vector3f) -> *const Medium { self.common.medium_interface.as_ref().and_then(|interface| { if self.n().dot(w.into()) > 0.0 { - interface.outside.clone() + interface.outside } else { - interface.inside.clone() + interface.inside } }) } @@ -543,7 +549,7 @@ impl SurfaceInteraction { dpdv, dndu, dndv, - shading: Shadinggeom { + shading: ShadingGeom { n: shading_n, dpdu, dpdv, @@ -559,7 +565,7 @@ impl SurfaceInteraction { dudy: 0.0, dvdx: 0.0, dvdy: 0.0, - shape: Arc::new(Shape::default()), + shape: core::ptr::null(), } } @@ -625,31 +631,32 @@ impl SurfaceInteraction { } } + #[cfg(not(target_os = "cuda"))] pub fn set_intersection_properties( &mut self, - mtl: Arc, - area: Arc, - prim_medium_interface: Option, - ray_medium: Arc, + mtl: *const Material, + area: *const Light, + prim_medium_interface: MediumInterface, + ray_medium: *const Medium, ) { - self.material = Some(mtl); - self.area_light = Some(area); - if prim_medium_interface - .as_ref() - .is_some_and(|mi| mi.is_medium_transition()) - { - self.common.medium_interface = prim_medium_interface; + self.material = mtl; + self.area_light = area; + + if prim_medium_interface.is_medium_transition() { + self.common.medium_interface = *prim_medium_interface; } else { - self.common.medium = Some(ray_medium); + self.common.medium = ray_medium; } } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct MediumInteraction { pub common: InteractionData, - pub medium: Arc, + pub medium: *const Medium, pub phase: PhaseFunction, + pub medium_interface: MediumInterface, } impl MediumInteraction { @@ -657,7 +664,7 @@ impl MediumInteraction { p: Point3f, wo: Vector3f, time: Float, - medium: Arc, + medium: *const Medium, phase: PhaseFunction, ) -> Self { Self { @@ -667,10 +674,11 @@ impl MediumInteraction { time, wo: wo.normalize(), medium_interface: None, - medium: Some(medium.clone()), + medium, }, medium, phase, + medium_interface: MediumInterface::empty(), } } } diff --git a/shared/src/core/light.rs b/shared/src/core/light.rs index 098fd66..d5782b9 100644 --- a/shared/src/core/light.rs +++ b/shared/src/core/light.rs @@ -1,38 +1,30 @@ +use crate::core::color::RGB; use crate::core::geometry::{ Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike, cos_theta, }; use crate::core::interaction::{ - Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction, + Interaction, InteractionData, InteractionTrait, MediumInteraction, SimpleInteraction, + SurfaceInteraction, }; use crate::core::medium::MediumInterface; -use crate::core::pbrt::{Float, PI}; +use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::images::Image; +use crate::lights::*; use crate::spectra::{ - DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGB, RGBColorSpace, RGBIlluminantSpectrum, - SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, + DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace, RGBIlluminantSpectrum, + SampledSpectrum, SampledWavelengths, }; -use crate::utils::containers::InternCache; +use crate::utils::Transform; use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square}; use crate::utils::sampling::PiecewiseConstant2D; -use crate::utils::transform::TransformGeneric; +use crate::{Float, PI}; use bitflags::bitflags; use enum_dispatch::enum_dispatch; -use std::sync::{Arc, OnceLock}; - -use crate::lights::diffuse::DiffuseAreaLight; -use crate::lights::infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight}; - -use crate::lights::sampler::{LightSampler, LightSamplerTrait}; - -static SPECTRUM_CACHE: OnceLock> = OnceLock::new(); - -fn get_spectrum_cache() -> &'static InternCache { - SPECTRUM_CACHE.get_or_init(InternCache::new) -} bitflags! { + #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct LightType: u32 { const DeltaPosition = 1; @@ -52,15 +44,37 @@ impl LightType { } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct LightLeSample { - l: SampledSpectrum, - ray: Ray, - intr: Option, - pdf_pos: Float, - pdf_dir: Float, + pub l: SampledSpectrum, + pub ray: Ray, + pub intr: *const InteractionData, + pub pdf_pos: Float, + pub pdf_dir: Float, } +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct LightLiSample { + pub l: SampledSpectrum, + pub wi: Vector3f, + pub pdf: Float, + pub p_light: InteractionData, +} + +#[cfg(not(target_os = "cuda"))] +impl LightLiSample { + pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: InteractionData) -> Self { + Self { + l, + wi, + pdf, + p_light, + } + } +} +#[repr(C)] #[derive(Debug, Copy, Default, Clone)] pub struct LightSampleContext { pub pi: Point3fi, @@ -122,38 +136,19 @@ impl From<&Interaction> for LightSampleContext { } } -#[derive(Debug, Clone)] -pub struct LightLiSample { - pub l: SampledSpectrum, - pub wi: Vector3f, - pub pdf: Float, - pub p_light: Arc, -} - -impl LightLiSample { - pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self { - Self { - l, - wi, - pdf, - p_light: Arc::new(p_light), - } - } -} - #[repr(C)] #[derive(Debug, Clone, Copy)] -pub struct LightBaseData { +pub struct LightBase { pub render_from_light: Transform, pub light_type: u32, - pub mi_inside: i32, - pub mi_outside: i32, + pub medium_interface: MediumInterface, } +#[cfg(not(target_os = "cuda"))] impl LightBase { pub fn new( light_type: LightType, - render_from_light: &TransformGeneric, + render_from_light: &Transform, medium_interface: &MediumInterface, ) -> Self { Self { @@ -163,6 +158,14 @@ impl LightBase { } } + pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrum { + let cache = SPECTRUM_CACHE.get_or_init(InternCache::new); + let dense_spectrum = DenselySampledSpectrum::from_spectrum(s); + cache.lookup(dense_spectrum).as_ref() + } +} + +impl LightBase { fn l( &self, _p: Point3f, @@ -181,24 +184,20 @@ impl LightBase { pub fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { SampledSpectrum::default() } - - pub fn lookup_spectrum(s: &Spectrum) -> Arc { - let cache = SPECTRUM_CACHE.get_or_init(InternCache::new); - let dense_spectrum = DenselySampledSpectrum::from_spectrum(s); - cache.lookup(dense_spectrum) - } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct LightBounds { - bounds: Bounds3f, - phi: Float, - w: Vector3f, - cos_theta_o: Float, - cos_theta_e: Float, - two_sided: bool, + pub bounds: Bounds3f, + pub phi: Float, + pub w: Vector3f, + pub cos_theta_o: Float, + pub cos_theta_e: Float, + pub two_sided: bool, } +#[cfg(not(target_os = "cuda"))] impl LightBounds { pub fn new( bounds: &Bounds3f, @@ -217,7 +216,9 @@ impl LightBounds { two_sided, } } +} +impl LightBounds { pub fn centroid(&self) -> Point3f { self.bounds.p_min + Vector3f::from(self.bounds.p_max) / 2. } @@ -301,9 +302,9 @@ impl LightBounds { } #[enum_dispatch] -pub trait LightTrait: Send + Sync + std::fmt::Debug { +pub trait LightTrait { fn base(&self) -> &LightBase; - fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum; + fn sample_li( &self, ctx: &LightSampleContext, @@ -311,7 +312,9 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug { lambda: &SampledWavelengths, allow_incomplete_pdf: bool, ) -> Option; + fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float; + fn l( &self, p: Point3f, @@ -320,16 +323,26 @@ pub trait LightTrait: Send + Sync + std::fmt::Debug { w: Vector3f, lambda: &SampledWavelengths, ) -> SampledSpectrum; + fn le(&self, ray: &Ray, lambda: &SampledWavelengths) -> SampledSpectrum; - fn preprocess(&mut self, scene_bounds: &Bounds3f); - fn bounds(&self) -> Option; + fn light_type(&self) -> LightType { - self.base().light_type() + self.base().light_type } + + #[cfg(not(target_os = "cuda"))] + fn bounds(&self) -> Option; + + #[cfg(not(target_os = "cuda"))] + fn preprocess(&mut self, scene_bounds: &Bounds3f); + + #[cfg(not(target_os = "cuda"))] + fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum; } -#[derive(Debug, Clone)] #[enum_dispatch(LightTrait)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] #[allow(clippy::large_enum_variant)] pub enum Light { DiffuseArea(DiffuseAreaLight), @@ -342,548 +355,3 @@ pub enum Light { Projection(ProjectionLight), Spot(SpotLight), } - -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct DistantLightData { - pub base: LightBaseData, - pub lemit_coeffs: [Float; 32], - pub scale: Float, - pub scene_center: Point3f, - pub scene_radius: Float, -} - -impl DistantLightData { - pub fn sample_li( - &self, - ctx_p: Point3f, - lambda: &SampledWavelengths, - ) -> (SampledSpectrum, Vector3f, Float, Point3f) { - let wi = self - .base - .render_from_light - .apply_to_vector(Vector3f::new(0., 0., 1.)) - .normalize(); - let p_outside = ctx_p + wi * 2. * self.scene_radius; - let spectrum = DenselySampledSpectrum::from_array(&self.lemit_coeffs); - let li = self.scale * spectrum.sample(lambda); - (li, wi, 1.0, p_outside) - } -} - -impl LightTrait for DistantLight { - fn base(&self) -> &LightBase { - &self.base - } - - fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { - self.scale * self.lemit.sample(&lambda) * PI * self.scene_radius.sqrt() - } - - fn sample_li( - &self, - ctx: &LightSampleContext, - _u: Point2f, - lambda: &SampledWavelengths, - _allow_incomplete_pdf: bool, - ) -> Option { - 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 { - None - } -} - -#[derive(Debug, Clone)] -pub struct GoniometricLight { - pub base: LightBase, - iemit: Arc, - scale: Float, - image: Image, - distrib: PiecewiseConstant2D, -} - -impl GoniometricLight { - pub fn new( - render_from_light: &TransformGeneric, - 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 { - 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 { - 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 { - 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 { - 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, - medium_interface: MediumInterface, - image: Image, - image_color_space: Arc, - 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 { - 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 { - 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 { - 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 { - 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, - )) - } -} diff --git a/shared/src/core/material.rs b/shared/src/core/material.rs index a336df1..cab3fcb 100644 --- a/shared/src/core/material.rs +++ b/shared/src/core/material.rs @@ -1,4 +1,3 @@ -use bumpalo::Bump; use enum_dispatch::enum_dispatch; use std::ops::Deref; use std::sync::Arc; @@ -9,18 +8,21 @@ use crate::core::bxdf::{ }; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike}; use crate::core::interaction::InteractionTrait; -use crate::core::interaction::{Interaction, Shadinggeom, SurfaceInteraction}; +use crate::core::interaction::{Interaction, ShadingGeom, SurfaceInteraction}; use crate::core::pbrt::Float; use crate::core::scattering::TrowbridgeReitzDistribution; +use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::texture::{ - FloatTexture, FloatTextureTrait, SpectrumTexture, TextureEvalContext, TextureEvaluator, + GPUFloatTexture, GPUSpectrumTexture, TextureEvalContext, TextureEvaluator, }; -use crate::image::{Image, WrapMode, WrapMode2D}; -use crate::spectra::{SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait}; +use crate::images::{Image, WrapMode, WrapMode2D}; +use crate::spectra::{SampledSpectrum, SampledWavelengths}; +use crate::utils::Ptr; use crate::utils::hash::hash_float; use crate::utils::math::clamp; -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct MaterialEvalContext { pub texture: TextureEvalContext, pub wo: Vector3f, @@ -47,20 +49,21 @@ impl From<&SurfaceInteraction> for MaterialEvalContext { } } +#[repr(C)] #[derive(Clone, Debug, Default)] pub struct NormalBumpEvalContext { - p: Point3f, - uv: Point2f, - n: Normal3f, - shading: Shadinggeom, - dpdx: Vector3f, - dpdy: Vector3f, + pub p: Point3f, + pub uv: Point2f, + pub n: Normal3f, + pub shading: ShadingGeom, + pub dpdx: Vector3f, + pub dpdy: Vector3f, // All 0 - dudx: Float, - dudy: Float, - dvdx: Float, - dvdy: Float, - face_index: usize, + pub dudx: Float, + pub dudy: Float, + pub dvdx: Float, + pub dvdy: Float, + pub face_index: usize, } impl From<&SurfaceInteraction> for NormalBumpEvalContext { @@ -117,7 +120,7 @@ pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f, pub fn bump_map( tex_eval: &T, - displacement: &FloatTexture, + displacement: &GPUFloatTexture, ctx: &NormalBumpEvalContext, ) -> (Vector3f, Vector3f) { debug_assert!(tex_eval.can_evaluate(&[displacement], &[])); @@ -168,12 +171,12 @@ pub trait MaterialTrait: Send + Sync + std::fmt::Debug { ) -> Option; fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool; - fn get_normal_map(&self) -> Option>; - fn get_displacement(&self) -> Option; + fn get_normal_map(&self) -> *const Image; + fn get_displacement(&self) -> Option; fn has_surface_scattering(&self) -> bool; } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] #[enum_dispatch(MaterialTrait)] pub enum Material { CoatedDiffuse(CoatedDiffuseMaterial), @@ -191,32 +194,33 @@ pub enum Material { #[derive(Clone, Debug)] pub struct CoatedDiffuseMaterial { - displacement: FloatTexture, - normal_map: Option>, - reflectance: SpectrumTexture, - albedo: SpectrumTexture, - u_roughness: FloatTexture, - v_roughness: FloatTexture, - thickness: FloatTexture, - g: FloatTexture, - eta: Spectrum, - remap_roughness: bool, - max_depth: usize, - n_samples: usize, + pub displacement: GPUFloatTexture, + pub normal_map: *const Image, + pub reflectance: GPUSpectrumTexture, + pub albedo: GPUSpectrumTexture, + pub u_roughness: GPUFloatTexture, + pub v_roughness: GPUFloatTexture, + pub thickness: GPUFloatTexture, + pub g: GPUFloatTexture, + pub eta: Spectrum, + pub remap_roughness: bool, + pub max_depth: usize, + pub n_samples: usize, } impl CoatedDiffuseMaterial { #[allow(clippy::too_many_arguments)] + #[cfg(not(target_os = "cuda"))] pub fn new( - reflectance: SpectrumTexture, - u_roughness: FloatTexture, - v_roughness: FloatTexture, - thickness: FloatTexture, - albedo: SpectrumTexture, - g: FloatTexture, + reflectance: GPUSpectrumTexture, + u_roughness: GPUFloatTexture, + v_roughness: GPUFloatTexture, + thickness: GPUFloatTexture, + albedo: GPUSpectrumTexture, + g: GPUFloatTexture, eta: Spectrum, - displacement: FloatTexture, - normal_map: Option>, + displacement: GPUFloatTexture, + normal_map: *const Image, remap_roughness: bool, max_depth: usize, n_samples: usize, @@ -313,8 +317,8 @@ impl MaterialTrait for CoatedDiffuseMaterial { ) } - fn get_normal_map(&self) -> Option> { - self.normal_map.clone() + fn get_normal_map(&self) -> *const Image { + self.normal_map } fn get_displacement(&self) -> Option { @@ -326,10 +330,11 @@ impl MaterialTrait for CoatedDiffuseMaterial { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct CoatedConductorMaterial { displacement: FloatTexture, - normal_map: Option>, + normal_map: *const Image, interface_uroughness: FloatTexture, interface_vroughness: FloatTexture, thickness: FloatTexture, @@ -348,6 +353,7 @@ pub struct CoatedConductorMaterial { impl CoatedConductorMaterial { #[allow(clippy::too_many_arguments)] + #[cfg(not(target_os = "cuda"))] pub fn new( displacement: FloatTexture, normal_map: Option>, @@ -502,8 +508,8 @@ impl MaterialTrait for CoatedConductorMaterial { tex_eval.can_evaluate(&float_textures, &spectrum_textures) } - fn get_normal_map(&self) -> Option> { - self.normal_map.clone() + fn get_normal_map(&self) -> *const Image { + self.normal_map } fn get_displacement(&self) -> Option { @@ -515,7 +521,8 @@ impl MaterialTrait for CoatedConductorMaterial { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct ConductorMaterial; impl MaterialTrait for ConductorMaterial { fn get_bsdf( @@ -537,7 +544,7 @@ impl MaterialTrait for ConductorMaterial { fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool { todo!() } - fn get_normal_map(&self) -> Option> { + fn get_normal_map(&self) -> *const Image { todo!() } fn get_displacement(&self) -> Option { @@ -547,9 +554,11 @@ impl MaterialTrait for ConductorMaterial { todo!() } } -#[derive(Clone, Debug)] + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct DielectricMaterial { - normal_map: Option>, + normal_map: *const Image, displacement: FloatTexture, u_roughness: FloatTexture, v_roughness: FloatTexture, @@ -612,9 +621,11 @@ impl MaterialTrait for DielectricMaterial { false } } -#[derive(Clone, Debug)] + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct DiffuseMaterial { - normal_map: Option>, + normal_map: *const Image, displacement: FloatTexture, reflectance: SpectrumTexture, } @@ -656,8 +667,11 @@ impl MaterialTrait for DiffuseMaterial { false } } -#[derive(Clone, Debug)] + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct DiffuseTransmissionMaterial; + impl MaterialTrait for DiffuseTransmissionMaterial { fn get_bsdf( &self, @@ -692,8 +706,11 @@ impl MaterialTrait for DiffuseTransmissionMaterial { todo!() } } -#[derive(Clone, Debug)] + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct HairMaterial; + impl MaterialTrait for HairMaterial { fn get_bsdf( &self, @@ -726,7 +743,8 @@ impl MaterialTrait for HairMaterial { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct MeasuredMaterial; impl MaterialTrait for MeasuredMaterial { fn get_bsdf( @@ -759,7 +777,9 @@ impl MaterialTrait for MeasuredMaterial { todo!() } } -#[derive(Clone, Debug)] + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct SubsurfaceMaterial; impl MaterialTrait for SubsurfaceMaterial { fn get_bsdf( @@ -792,7 +812,9 @@ impl MaterialTrait for SubsurfaceMaterial { todo!() } } -#[derive(Clone, Debug)] + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct ThinDielectricMaterial; impl MaterialTrait for ThinDielectricMaterial { fn get_bsdf( @@ -824,10 +846,12 @@ impl MaterialTrait for ThinDielectricMaterial { todo!() } } -#[derive(Clone, Debug)] + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct MixMaterial { pub amount: FloatTexture, - pub materials: [Box; 2], + pub materials: [Ptr; 2], } impl MixMaterial { @@ -835,23 +859,19 @@ impl MixMaterial { &self, tex_eval: &T, ctx: &MaterialEvalContext, - ) -> &Material { + ) -> Option<&Material> { let amt = tex_eval.evaluate_float(&self.amount, ctx); - if amt <= 0.0 { - return &self.materials[0]; - } - if amt >= 1.0 { - return &self.materials[1]; - } - - let u = hash_float(&(ctx.p, ctx.wo)); - - if amt < u { - &self.materials[0] + let index = if amt <= 0.0 { + 0 + } else if amt >= 1.0 { + 1 } else { - &self.materials[1] - } + let u = hash_float(&(ctx.p, ctx.wo)); + if amt < u { 0 } else { 1 } + }; + + self.materials[index].get() } } @@ -862,8 +882,11 @@ impl MaterialTrait for MixMaterial { ctx: &MaterialEvalContext, lambda: &SampledWavelengths, ) -> BSDF { - let chosen_mat = self.choose_material(tex_eval, ctx); - chosen_mat.get_bsdf(tex_eval, ctx, lambda) + if let Some(mat) = self.choose_material(tex_eval, ctx) { + mat.get_bsdf(tex_eval, ctx, lambda) + } else { + BSDF::empty() + } } fn get_bssrdf( diff --git a/shared/src/core/medium.rs b/shared/src/core/medium.rs index 4d3d5e2..2fd60e3 100644 --- a/shared/src/core/medium.rs +++ b/shared/src/core/medium.rs @@ -1,4 +1,3 @@ -use bumpalo::Bump; use enum_dispatch::enum_dispatch; use std::sync::Arc; @@ -8,13 +7,14 @@ use crate::core::geometry::{ use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::spectra::{ BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, - RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, + RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, }; use crate::utils::containers::SampledGrid; use crate::utils::math::{clamp, square}; use crate::utils::rng::Rng; use crate::utils::transform::TransformGeneric; +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct PhaseFunctionSample { pub p: Float, @@ -35,12 +35,14 @@ pub trait PhaseFunctionTrait { fn pdf(&self, wo: Vector3f, wi: Vector3f) -> Float; } +#[repr(C)] #[enum_dispatch(PhaseFunctionTrait)] #[derive(Debug, Clone)] pub enum PhaseFunction { HenyeyGreenstein(HGPhaseFunction), } +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct HGPhaseFunction { g: Float, @@ -85,14 +87,19 @@ impl PhaseFunctionTrait for HGPhaseFunction { } } +#[repr(C)] #[derive(Debug, Clone)] pub struct MajorantGrid { pub bounds: Bounds3f, pub res: Point3i, - pub voxels: Vec, + pub voxels: *const Float, } +unsafe impl Send for MajorantGrid {} +unsafe impl Sync for MajorantGrid {} + impl MajorantGrid { + #[cfg(not(target_os = "cuda"))] pub fn new(bounds: Bounds3f, res: Point3i) -> Self { Self { bounds, @@ -100,18 +107,37 @@ impl MajorantGrid { voxels: Vec::with_capacity((res.x() * res.y() * res.z()) as usize), } } + + #[inline(always)] + fn is_valid(&self) -> bool { + !self.voxels.is_null() + } + + #[inline(always)] pub fn lookup(&self, x: i32, y: i32, z: i32) -> Float { + if !self.is_valid() { + return 0.0; + } + let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x; + if idx >= 0 && (idx as usize) < self.voxels.len() { - self.voxels[idx as usize] + unsafe { *self.voxels.add(idx as usize) } } else { 0.0 } } - pub fn set(&mut self, x: i32, y: i32, z: i32, v: Float) { - let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x; - if idx >= 0 && (idx as usize) < self.voxels.len() { - self.voxels[idx as usize] = v; + + #[inline(always)] + pub fn set(&self, x: i32, y: i32, z: i32, v: Float) { + if !self.is_valid() { + return; + } + + let idx = x + self.res.x() * (y + self.res.y() * z); + + unsafe { + *self.voxels.add(idx as usize) = v; } } @@ -130,6 +156,7 @@ impl MajorantGrid { } } +#[repr(C)] #[derive(Clone, Copy, Debug)] pub struct RayMajorantSegment { t_min: Float, @@ -137,6 +164,8 @@ pub struct RayMajorantSegment { sigma_maj: SampledSpectrum, } +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub enum RayMajorantIterator { Homogeneous(HomogeneousMajorantIterator), DDA(DDAMajorantIterator), @@ -155,6 +184,9 @@ impl Iterator for RayMajorantIterator { } } } + +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct HomogeneousMajorantIterator { called: bool, seg: RayMajorantSegment, @@ -186,6 +218,8 @@ impl Iterator for HomogeneousMajorantIterator { } } +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct DDAMajorantIterator { sigma_t: SampledSpectrum, t_min: Float, @@ -197,6 +231,7 @@ pub struct DDAMajorantIterator { voxel_limit: [i32; 3], voxel: [i32; 3], } + impl DDAMajorantIterator { pub fn new( ray: &Ray, @@ -209,7 +244,7 @@ impl DDAMajorantIterator { t_min, t_max, sigma_t: *sigma_t, - grid: grid.clone(), + grid: *grid, next_crossing_t: [0.0; 3], delta_t: [0.0; 3], step: [0; 3], @@ -227,28 +262,29 @@ impl DDAMajorantIterator { let p_grid_start = grid.bounds.offset(&ray.at(t_min)); let grid_intersect = Vector3f::from(p_grid_start); + let res = [grid.res.x, grid.res.y, grid.res.z]; for axis in 0..3 { iter.voxel[axis] = clamp( - (grid_intersect[axis] * grid.res[axis] as Float) as i32, + (grid_intersect[axis] * res[axis] as Float) as i32, 0, - grid.res[axis] - 1, + res[axis] - 1, ); - iter.delta_t[axis] = 1.0 / (ray_grid_d[axis].abs() * grid.res[axis] as Float); + iter.delta_t[axis] = 1.0 / (ray_grid_d[axis].abs() * res[axis] as Float); if ray_grid_d[axis] == -0.0 { ray_grid_d[axis] = 0.0; } if ray_grid_d[axis] >= 0.0 { - let next_voxel_pos = (iter.voxel[axis] + 1) as Float / grid.res[axis] as Float; + let next_voxel_pos = (iter.voxel[axis] + 1) as Float / res[axis] as Float; iter.next_crossing_t[axis] = t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis]; iter.step[axis] = 1; - iter.voxel_limit[axis] = grid.res[axis]; + iter.voxel_limit[axis] = res[axis]; } else { - let next_voxel_pos = (iter.voxel[axis]) as Float / grid.res[axis] as Float; + let next_voxel_pos = (iter.voxel[axis]) as Float / res[axis] as Float; iter.next_crossing_t[axis] = t_min + (next_voxel_pos - grid_intersect[axis]) / ray_grid_d[axis]; iter.step[axis] = -1; @@ -313,6 +349,8 @@ impl Iterator for DDAMajorantIterator { } } +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct MediumProperties { pub sigma_a: SampledSpectrum, pub sigma_s: SampledSpectrum, @@ -335,7 +373,6 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug { ray: &Ray, t_max: Float, lambda: &SampledWavelengths, - buf: &Bump, ) -> RayMajorantIterator; fn sample_t_maj( @@ -354,8 +391,7 @@ pub trait MediumTrait: Send + Sync + std::fmt::Debug { t_max *= len; ray.d /= len; - let buf = Bump::new(); - let mut iter = self.sample_ray(&ray, t_max, lambda, &buf); + let mut iter = self.sample_ray(&ray, t_max, lambda); let mut t_maj = SampledSpectrum::new(1.0); while let Some(seg) = iter.next() { @@ -463,7 +499,6 @@ impl MediumTrait for HomogeneousMedium { _ray: &Ray, t_max: Float, lambda: &SampledWavelengths, - _scratch: &Bump, ) -> RayMajorantIterator { let sigma_a = self.sigma_a_spec.sample(lambda); let sigma_s = self.sigma_s_spec.sample(lambda); @@ -475,7 +510,8 @@ impl MediumTrait for HomogeneousMedium { } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct GridMedium { bounds: Bounds3f, render_from_medium: TransformGeneric, @@ -483,7 +519,7 @@ pub struct GridMedium { sigma_s_spec: DenselySampledSpectrum, density_grid: SampledGrid, phase: HGPhaseFunction, - temperature_grid: Option>, + temperature_grid: SampledGrid, le_spec: DenselySampledSpectrum, le_scale: SampledGrid, is_emissive: bool, @@ -492,15 +528,16 @@ pub struct GridMedium { impl GridMedium { #[allow(clippy::too_many_arguments)] + #[cfg(not(target_os = "cuda"))] pub fn new( bounds: &Bounds3f, - render_from_medium: &TransformGeneric, + render_from_medium: &Transform, sigma_a: &Spectrum, sigma_s: &Spectrum, sigma_scale: Float, g: Float, density_grid: SampledGrid, - temperature_grid: Option>, + temperature_grid: SampledGrid, le: &Spectrum, le_scale: SampledGrid, ) -> Self { @@ -588,7 +625,6 @@ impl MediumTrait for GridMedium { ray: &Ray, t_max: Float, lambda: &SampledWavelengths, - _buf: &Bump, ) -> RayMajorantIterator { let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max)); @@ -621,29 +657,31 @@ impl MediumTrait for GridMedium { } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct RGBGridMedium { bounds: Bounds3f, - render_from_medium: TransformGeneric, - le_grid: Option>, - le_scale: Float, + render_from_medium: Transform, phase: HGPhaseFunction, - sigma_a_grid: Option>, - sigma_s_grid: Option>, + le_scale: Float, sigma_scale: Float, + sigma_a_grid: SampledGrid, + sigma_s_grid: SampledGrid, + le_grid: SampledGrid, majorant_grid: MajorantGrid, } impl RGBGridMedium { #[allow(clippy::too_many_arguments)] + #[cfg(not(target_os = "cuda"))] pub fn new( bounds: &Bounds3f, render_from_medium: &TransformGeneric, g: Float, - sigma_a_grid: Option>, - sigma_s_grid: Option>, + sigma_a_grid: SampledGrid, + sigma_s_grid: SampledGrid, sigma_scale: Float, - le_grid: Option>, + le_grid: SampledGrid, le_scale: Float, ) -> Self { let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16)); @@ -679,31 +717,29 @@ impl RGBGridMedium { impl MediumTrait for RGBGridMedium { fn is_emissive(&self) -> bool { - self.le_grid.is_some() && self.le_scale > 0. + self.le_grid.is_valid() && self.le_scale > 0. } fn sample_point(&self, p: Point3f, lambda: &SampledWavelengths) -> MediumProperties { let p_transform = self.render_from_medium.apply_inverse(p); let p = Point3f::from(self.bounds.offset(&p_transform)); let convert = |s: &RGBUnboundedSpectrum| s.sample(lambda); - let sigma_a = self.sigma_scale - * self - .sigma_a_grid - .as_ref() - .map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert)); + let sigma_a = if self.sigma_a_grid.is_valid() { + self.sigma_scale * self.sigma_a_grid.lookup_convert(p, convert) + } else { + SampledSpectrum::new(1.0) * self.sigma_scale // Or 0.0? Check PBRT logic + }; + let sigma_s = if self.sigma_s_grid.is_valid() { + self.sigma_scale * self.sigma_s_grid.lookup_convert(p, convert) + } else { + SampledSpectrum::new(1.0) * self.sigma_scale + }; - let sigma_s = self.sigma_scale - * self - .sigma_s_grid - .as_ref() - .map_or(SampledSpectrum::new(1.0), |g| g.lookup_convert(p, convert)); - - let le = self - .le_grid - .as_ref() - .filter(|_| self.le_scale > 0.0) - .map(|g| g.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale) - .unwrap_or_else(|| SampledSpectrum::new(0.0)); + let le = if self.le_grid.is_valid() && self.le_scale > 0.0 { + self.le_grid.lookup_convert(p, |s| s.sample(lambda)) * self.le_scale + } else { + SampledSpectrum::new(0.0) + }; MediumProperties { sigma_a, @@ -718,7 +754,6 @@ impl MediumTrait for RGBGridMedium { ray: &Ray, t_max: Float, _lambda: &SampledWavelengths, - _buf: &Bump, ) -> RayMajorantIterator { let (local_ray, local_t_max) = self.render_from_medium.apply_inverse_ray(ray, Some(t_max)); @@ -732,6 +767,7 @@ impl MediumTrait for RGBGridMedium { if local_ray.d.y() < 0.0 { 1 } else { 0 }, if local_ray.d.z() < 0.0 { 1 } else { 0 }, ]; + let (t_min, t_max) = match self .bounds @@ -763,7 +799,6 @@ impl MediumTrait for CloudMedium { _ray: &Ray, _t_max: Float, _lambda: &SampledWavelengths, - _buf: &Bump, ) -> RayMajorantIterator { todo!() } @@ -782,28 +817,43 @@ impl MediumTrait for NanoVDBMedium { _ray: &Ray, _t_max: Float, _lambda: &SampledWavelengths, - _buf: &Bump, ) -> RayMajorantIterator { todo!() } } -#[derive(Debug, Default, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct MediumInterface { - pub inside: Option>, - pub outside: Option>, + pub inside: *const Medium, + pub outside: *const Medium, } -impl MediumInterface { - pub fn new(inside: Option>, outside: Option>) -> Self { - Self { inside, outside } - } +unsafe impl Send for MediumInterface {} +unsafe impl Sync for MediumInterface {} - pub fn is_medium_transition(&self) -> bool { - match (&self.inside, &self.outside) { - (Some(inside), Some(outside)) => !Arc::ptr_eq(inside, outside), - (None, None) => false, - _ => true, +impl Default for MediumInterface { + fn default() -> Self { + Self { + inside: core::ptr::null(), + outside: core::ptr::null(), } } } + +impl MediumInterface { + pub fn new(inside: *const Medium, outside: *const Medium) -> Self { + Self { inside, outside } + } + + pub fn empty() -> Self { + Self { + inside: core::ptr::null(), + outside: core::ptr::null(), + } + } + + pub fn is_medium_transition(&self) -> bool { + self.inside != self.outside + } +} diff --git a/shared/src/core/mod.rs b/shared/src/core/mod.rs index ae16805..de4ab79 100644 --- a/shared/src/core/mod.rs +++ b/shared/src/core/mod.rs @@ -2,6 +2,7 @@ pub mod aggregates; pub mod bssrdf; pub mod bxdf; pub mod camera; +pub mod color; pub mod film; pub mod filter; pub mod geometry; diff --git a/shared/src/core/primitive.rs b/shared/src/core/primitive.rs index 776d5d3..45f090b 100644 --- a/shared/src/core/primitive.rs +++ b/shared/src/core/primitive.rs @@ -1,11 +1,11 @@ use crate::core::aggregates::LinearBVHNode; use crate::core::geometry::{Bounds3f, Ray}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; +use crate::core::light::Light; use crate::core::material::Material; use crate::core::medium::{Medium, MediumInterface}; use crate::core::pbrt::Float; -use crate::core::texture::{FloatTextureTrait, TextureEvalContext}; -use crate::lights::Light; +use crate::core::texture::{GPUFloatTexture, TextureEvalContext}; use crate::shapes::{Shape, ShapeIntersection, ShapeTrait}; use crate::utils::hash::hash_float; use crate::utils::transform::{AnimatedTransform, TransformGeneric}; @@ -14,21 +14,25 @@ use enum_dispatch::enum_dispatch; use std::sync::Arc; #[enum_dispatch] -pub trait PrimitiveTrait: Send + Sync + std::fmt::Debug { +pub trait PrimitiveTrait { fn bounds(&self) -> Bounds3f; fn intersect(&self, r: &Ray, t_max: Option) -> Option; fn intersect_p(&self, r: &Ray, t_max: Option) -> bool; } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct GeometricPrimitive { - shape: Arc, - material: Arc, - area_light: Arc, + shape: *const Shape, + material: *const Material, + area_light: *const Light, medium_interface: MediumInterface, - alpha: Option>, + alpha: *const GPUFloatTexture, } +unsafe impl Send for GeometricPrimitive {} +unsafe impl Sync for GeometricPrimitive {} + impl PrimitiveTrait for GeometricPrimitive { fn bounds(&self) -> Bounds3f { self.shape.bounds() @@ -80,8 +84,9 @@ impl PrimitiveTrait for GeometricPrimitive { } } -#[derive(Debug, Clone)] -pub struct SimplePrimitiv { +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SimplePrimitive { shape: Arc, material: Arc, } diff --git a/shared/src/core/spectrum.rs b/shared/src/core/spectrum.rs index e69de29..ee412f2 100644 --- a/shared/src/core/spectrum.rs +++ b/shared/src/core/spectrum.rs @@ -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(_)) + } +} diff --git a/shared/src/core/texture.rs b/shared/src/core/texture.rs index 8b4dc95..8c36767 100644 --- a/shared/src/core/texture.rs +++ b/shared/src/core/texture.rs @@ -1,20 +1,21 @@ +use crate::core::color::ColorEncoding; use crate::core::geometry::{ Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike, spherical_phi, spherical_theta, }; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; -use crate::core::pbrt::Float; -use crate::core::pbrt::{INV_2_PI, INV_PI, PI}; use crate::images::WrapMode; -use crate::spectra::color::ColorEncoding; use crate::spectra::{ - RGB, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, - SampledWavelengths, Spectrum, SpectrumTrait, + RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, + SampledWavelengths, }; use crate::textures::*; use crate::utils::Transform; use crate::utils::math::square; +use crate::{Float, INV_2_PI, INV_PI, PI}; +use enum_dispatch::enum_dispatch; #[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct TexCoord2D { pub st: Point2f, pub dsdx: Float, @@ -24,7 +25,7 @@ pub struct TexCoord2D { } #[repr(C)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum TextureMapping2D { UV(UVMapping), Spherical(SphericalMapping), @@ -43,12 +44,13 @@ impl TextureMapping2D { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct UVMapping { - su: Float, - sv: Float, - du: Float, - dv: Float, + pub su: Float, + pub sv: Float, + pub du: Float, + pub dv: Float, } impl Default for UVMapping { @@ -83,7 +85,8 @@ impl UVMapping { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct SphericalMapping { texture_from_render: Transform, } @@ -124,7 +127,8 @@ impl SphericalMapping { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct CylindricalMapping { texture_from_render: Transform, } @@ -158,7 +162,8 @@ impl CylindricalMapping { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct PlanarMapping { texture_from_render: Transform, vs: Vector3f, @@ -203,6 +208,8 @@ impl PlanarMapping { } } +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct TexCoord3D { pub p: Point3f, pub dpdx: Vector3f, @@ -213,7 +220,8 @@ pub trait TextureMapping3DTrait { fn map(&self, ctx: &TextureEvalContext) -> TexCoord3D; } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub enum TextureMapping3D { PointTransform(PointTransformMapping), } @@ -226,15 +234,17 @@ impl TextureMapping3D { } } -#[derive(Clone, Debug)] +#[repr(C)] +#[derive(Clone, Debug, Copy)] pub struct PointTransformMapping { - texture_from_render: Transform, + pub texture_from_render: Transform, } impl PointTransformMapping { - pub fn new(texture_from_render: &Transform) -> Self { + #[cfg(not(target_os = "cuda"))] + pub fn new(texture_from_render: Transform) -> Self { Self { - texture_from_render: *texture_from_render, + texture_from_render, } } @@ -247,7 +257,8 @@ impl PointTransformMapping { } } -#[derive(Clone, Default, Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] pub struct TextureEvalContext { pub p: Point3f, pub dpdx: Vector3f, @@ -325,10 +336,11 @@ impl From<&Interaction> for TextureEvalContext { } #[repr(C)] +#[derive(Clone, Copy, Debug)] pub enum GPUFloatTexture { Constant(FloatConstantTexture), - DirectionMix(FloatDirectionMixTexture), - Scaled(FloatScaledTexture), + DirectionMix(GPUFloatDirectionMixTexture), + Scaled(GPUFloatScaledTexture), Bilerp(FloatBilerpTexture), Checkerboard(FloatCheckerboardTexture), Dots(FloatDotsTexture), @@ -359,6 +371,7 @@ impl GPUFloatTexture { } } +#[repr(C)] #[derive(Clone, Copy, Debug)] pub enum SpectrumType { Illuminant, @@ -367,14 +380,16 @@ pub enum SpectrumType { } #[repr(C)] +#[enum_dispatch] +#[derive(Clone, Copy, Debug)] pub enum GPUSpectrumTexture { Constant(SpectrumConstantTexture), Bilerp(SpectrumBilerpTexture), Checkerboard(SpectrumCheckerboardTexture), Marble(MarbleTexture), - DirectionMix(SpectrumDirectionMixTexture), + DirectionMix(GPUSpectrumDirectionMixTexture), Dots(SpectrumDotsTexture), - Scaled(SpectrumScaledTexture), + Scaled(GPUSpectrumScaledTexture), Image(GPUSpectrumImageTexture), Ptex(GPUSpectrumPtexTexture), Mix(GPUSpectrumMixTexture), @@ -396,3 +411,42 @@ impl GPUSpectrumTexture { } } } + +pub trait TextureEvaluator: Send + Sync { + fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float; + fn evaluate_spectrum( + &self, + tex: &GPUSpectrumTexture, + ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum; + + fn can_evaluate(&self, _ftex: &[&GPUFloatTexture], _stex: &[&GPUSpectrumTexture]) -> bool; +} + +#[repr(C)] +#[derive(Copy, Clone, Default)] +pub struct UniversalTextureEvaluator; + +impl TextureEvaluator for UniversalTextureEvaluator { + fn evaluate_float(&self, tex: &GPUFloatTexture, ctx: &TextureEvalContext) -> Float { + tex.evaluate(ctx) + } + + fn evaluate_spectrum( + &self, + tex: &GPUSpectrumTexture, + ctx: &TextureEvalContext, + lambda: &SampledWavelengths, + ) -> SampledSpectrum { + tex.evaluate(ctx, lambda) + } + + fn can_evaluate( + &self, + _float_textures: &[&GPUFloatTexture], + _spectrum_textures: &[&GPUSpectrumTexture], + ) -> bool { + true + } +} diff --git a/shared/src/filters/mod.rs b/shared/src/filters/mod.rs index f36bc92..4359359 100644 --- a/shared/src/filters/mod.rs +++ b/shared/src/filters/mod.rs @@ -3,3 +3,9 @@ pub mod gaussian; pub mod lanczos; pub mod mitchell; pub mod triangle; + +pub use boxf::*; +pub use gaussian::*; +pub use lanczos::*; +pub use mitchell::*; +pub use triangle::*; diff --git a/shared/src/integrators/mod.rs b/shared/src/integrators/mod.rs index e1050f0..ffe9087 100644 --- a/shared/src/integrators/mod.rs +++ b/shared/src/integrators/mod.rs @@ -2,10 +2,10 @@ mod pipeline; use pipeline::*; -use crate::camera::{Camera, CameraTrait}; use crate::core::bssrdf::{BSSRDFTrait, SubsurfaceInteraction}; use crate::core::bxdf::{BSDF, BxDFFlags, BxDFReflTransFlags, BxDFTrait, FArgs, TransportMode}; -use crate::core::film::{FilmTrait, VisibleSurface}; +use crate::core::camera::Camera; +use crate::core::film::VisibleSurface; use crate::core::geometry::{Bounds2i, Point2f, Point2i, Point3fi, Ray, Vector3f, VectorLike}; use crate::core::interaction::{ self, Interaction, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction, @@ -15,8 +15,8 @@ use crate::core::options::get_options; use crate::core::pbrt::{Float, SHADOW_EPSILON}; use crate::core::primitive::{Primitive, PrimitiveTrait}; use crate::core::sampler::{CameraSample, Sampler, SamplerTrait}; +use crate::lights::sampler::LightSamplerTrait; use crate::lights::sampler::{LightSampler, UniformLightSampler}; -use crate::lights::{Light, LightSampleContext, LightTrait, sampler::LightSamplerTrait}; use crate::shapes::ShapeIntersection; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::hash::{hash_buffer, mix_bits}; diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index 5f76fb4..ff54d80 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -1,26 +1,259 @@ +use crate::PI; +use crate::core::color::{RGB, XYZ}; use crate::core::geometry::*; +use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction}; +use crate::core::light::{ + LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, +}; +use crate::core::medium::MediumInterface; use crate::core::pbrt::Float; -// use crate::core::texture::GpuFloatTextureHandle; -// use crate::shapes::GpuShapeHandle; +use crate::core::spectrum::{Spectrum, SpectrumTrait}; +use crate::core::texture::{GPUFloatTexture, TextureEvalContext, UniversalTextureEvaluator}; +use crate::images::Image; +use crate::shapes::{Shape, ShapeSampleContext}; use crate::spectra::*; +use crate::utils::Transform; +use crate::utils::hash::hash_float; -#[repr(C)] -#[derive(Clone, Copy, Default)] +#[derive(Clone, Debug)] pub struct DiffuseAreaLight { - pub lemit_coeffs: [Float; 32], - pub scale: Float, + pub base: LightBase, + pub shape: *const Shape, + pub alpha: *const GPUFloatTexture, pub area: Float, pub two_sided: bool, - pub image_id: i32, - pub shape_id: u32, + pub lemit: DenselySampledSpectrum, + pub scale: Float, + pub image: *const Image, + pub image_color_space: RGBColorSpace, } -impl LightTrait for DiffuseAreaLight { - fn l(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { +#[cfg(not(target_os = "cuda"))] +impl DiffuseAreaLight { + #[allow(clippy::too_many_arguments)] + pub fn new( + render_from_light: Transform, + medium_interface: MediumInterface, + le: Spectrum, + scale: Float, + shape: Shape, + alpha: *const GPUFloatTexture, + image: *const Image, + image_color_space: *const RGBColorSpace, + two_sided: bool, + ) -> Self { + let is_constant_zero = match &alpha { + GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, + _ => false, + }; + + let (light_type, stored_alpha) = if is_constant_zero { + (LightType::DeltaPosition, None) + } else { + (LightType::Area, Some(alpha)) + }; + + let base = LightBase::new(light_type, &render_from_light, &medium_interface); + + let lemit = LightBase::lookup_spectrum(&le); + if let Some(im) = &image { + let desc = im + .get_channel_desc(&["R", "G", "B"]) + .expect("Image used for DiffuseAreaLight doesn't have R, G, B channels"); + + assert_eq!(3, desc.size(), "Image channel description size mismatch"); + assert!( + desc.is_identity(), + "Image channel description is not identity" + ); + + assert!( + image_color_space.is_some(), + "Image provided but ColorSpace is missing" + ); + } + let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_)); + if render_from_light.has_scale(None) && !is_triangle_or_bilinear { + println!( + "Scaling detected in rendering to light space transformation! \ + The system has numerous assumptions, implicit and explicit, \ + that this transform will have no scale factors in it. \ + Proceed at your own risk; your image may have errors." + ); + } + + Self { + base, + area: shape.area(), + shape, + alpha: stored_alpha, + two_sided, + lemit, + scale, + image, + image_color_space, + } + } + + fn l_base(&self, n: Normal3f, wo: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { if !self.two_sided && n.dot(wo) <= 0.0 { return SampledSpectrum::new(0.0); } let spec = DenselySampledSpectrum::from_array(&self.lemit_coeffs); spec.sample(lambda) * self.scale } + + fn alpha_masked(&self, intr: &Interaction) -> bool { + let Some(alpha_tex) = &self.alpha else { + return false; + }; + let ctx = TextureEvalContext::from(intr); + let a = UniversalTextureEvaluator.evaluate_float(alpha_tex, &ctx); + if a >= 1.0 { + return false; + } + if a <= 0.0 { + return true; + } + hash_float(&intr.p()) > a + } +} + +impl LightTrait for DiffuseAreaLight { + fn base(&self) -> &LightBase { + &self.base + } + + fn sample_li( + &self, + ctx: &LightSampleContext, + u: Point2f, + lambda: &SampledWavelengths, + _allow_incomplete_pdf: bool, + ) -> Option { + 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 { + 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, + )) + } } diff --git a/shared/src/lights/distant.rs b/shared/src/lights/distant.rs new file mode 100644 index 0000000..62cb906 --- /dev/null +++ b/shared/src/lights/distant.rs @@ -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 { + 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 { + None + } +} diff --git a/shared/src/lights/goniometric.rs b/shared/src/lights/goniometric.rs new file mode 100644 index 0000000..ef49c99 --- /dev/null +++ b/shared/src/lights/goniometric.rs @@ -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 { + 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 { + 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 + } +} diff --git a/shared/src/lights/infinite.rs b/shared/src/lights/infinite.rs index 07952d1..dc223e5 100644 --- a/shared/src/lights/infinite.rs +++ b/shared/src/lights/infinite.rs @@ -11,30 +11,19 @@ use crate::{ }, }; -use rayon::prelude::*; - -use crate::image::{PixelFormat, WrapMode}; - -use std::path::Path; - -use super::{ - Arc, Bounds2f, Bounds3f, DenselySampledSpectrum, Float, Image, Interaction, LightBaseData, - LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, MediumInterface, - Normal3f, PI, Point2f, Point2i, Point3f, Ray, SampledSpectrum, SampledWavelengths, - SimpleInteraction, Spectrum, TransformGeneric, Vector3f, VectorLike, - equal_area_sphere_to_square, square, -}; +use crate::images::{PixelFormat, WrapMode}; #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub struct InfiniteUniformLight { - base: LightBaseData, - lemit: u32, - scale: Float, - scene_center: Point3f, - scene_radius: Float, + pub base: LightBase, + pub lemit: u32, + pub scale: Float, + pub scene_center: Point3f, + pub scene_radius: Float, } +#[cfg(not(target_os = "cuda"))] impl InfiniteUniformLight { pub fn new(render_from_light: TransformGeneric, le: Spectrum, scale: Float) -> Self { let base = LightBase::new( @@ -83,10 +72,6 @@ impl LightTrait for InfiniteUniformLight { )) } - fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { - 4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda) - } - fn pdf_li( &self, _ctx: &LightSampleContext, @@ -119,6 +104,9 @@ impl LightTrait for InfiniteUniformLight { fn bounds(&self) -> Option { todo!() } + fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { + 4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda) + } } #[derive(Clone, Debug)] @@ -133,6 +121,7 @@ pub struct InfiniteImageLight { compensated_distrib: PiecewiseConstant2D, } +#[cfg(not(target_os = "cuda"))] impl InfiniteImageLight { pub fn new( render_from_light: TransformGeneric, @@ -213,6 +202,7 @@ impl LightTrait for InfiniteImageLight { fn base(&self) -> &LightBase { &self.base } + fn sample_li( &self, ctx: &LightSampleContext, @@ -245,30 +235,6 @@ impl LightTrait for InfiniteImageLight { Some(LightLiSample::new(self.image_le(uv, lambda), wi, pdf, intr)) } - fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { - let mut sum_l = SampledSpectrum::new(0.); - let width = self.image.resolution.x(); - let height = self.image.resolution.y(); - for v in 0..height { - for u in 0..width { - let mut rgb = RGB::default(); - for c in 0..3 { - rgb[c] = self.image.get_channel_with_wrap( - Point2i::new(u, v), - c, - WrapMode::OctahedralSphere.into(), - ); - } - sum_l += RGBIlluminantSpectrum::new( - self.image_color_space.as_ref(), - RGB::clamp_zero(rgb), - ) - .sample(&lambda); - } - } - 4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float - } - fn pdf_li(&self, _ctx: &LightSampleContext, wi: Vector3f, allow_incomplete_pdf: bool) -> Float { let w_light = self.base.render_from_light.apply_inverse_vector(wi); let uv = equal_area_sphere_to_square(w_light); @@ -301,22 +267,50 @@ impl LightTrait for InfiniteImageLight { self.image_le(uv, lambda) } + #[cfg(not(target_os = "cuda"))] + fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { + let mut sum_l = SampledSpectrum::new(0.); + let width = self.image.resolution.x(); + let height = self.image.resolution.y(); + for v in 0..height { + for u in 0..width { + let mut rgb = RGB::default(); + for c in 0..3 { + rgb[c] = self.image.get_channel_with_wrap( + Point2i::new(u, v), + c, + WrapMode::OctahedralSphere.into(), + ); + } + sum_l += RGBIlluminantSpectrum::new( + self.image_color_space.as_ref(), + RGB::clamp_zero(rgb), + ) + .sample(&lambda); + } + } + 4. * PI * PI * square(self.scene_radius) * self.scale * sum_l / (width * height) as Float + } + + #[cfg(not(target_os = "cuda"))] fn preprocess(&mut self, scene_bounds: &Bounds3f) { let (scene_center, scene_radius) = scene_bounds.bounding_sphere(); self.scene_center = scene_center; self.scene_radius = scene_radius; } + #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { None } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct InfinitePortalLight { - pub base: LightBaseData, + pub base: LightBase, pub image: Image, - pub image_color_space: Arc, + pub image_color_space: RGBColorSpace, pub scale: Float, pub filename: String, pub portal: [Point3f; 4], @@ -326,10 +320,8 @@ pub struct InfinitePortalLight { pub scene_radius: Float, } +#[cfg(not(target_os = "cuda"))] impl InfinitePortalLight { - fn base(&self) -> &LightBase { - &self.base - } pub fn new( render_from_light: TransformGeneric, equal_area_image: &Image, @@ -520,6 +512,7 @@ impl LightTrait for InfinitePortalLight { fn base(&self) -> &LightBase { &self.base } + fn sample_li( &self, ctx: &LightSampleContext, @@ -541,10 +534,6 @@ impl LightTrait for InfinitePortalLight { Some(LightLiSample::new(l, wi, pdf, intr)) } - fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum { - todo!() - } - fn pdf_li(&self, ctx: &LightSampleContext, wi: Vector3f, _allow_incomplete_pdf: bool) -> Float { let Some((uv, duv_dw)) = self.image_from_render(wi) else { return 0.; @@ -576,10 +565,17 @@ impl LightTrait for InfinitePortalLight { } } + #[cfg(not(target_os = "cuda"))] + fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum { + todo!() + } + + #[cfg(not(target_os = "cuda"))] fn preprocess(&mut self, _scene_bounds: &Bounds3f) { todo!() } + #[cfg(not(target_os = "cuda"))] fn bounds(&self) -> Option { None } diff --git a/shared/src/lights/mod.rs b/shared/src/lights/mod.rs index 1103957..5b2c3e6 100644 --- a/shared/src/lights/mod.rs +++ b/shared/src/lights/mod.rs @@ -1,3 +1,15 @@ pub mod diffuse; +pub mod distant; +pub mod goniometric; pub mod infinite; +pub mod point; +pub mod projection; pub mod sampler; +pub mod spot; + +pub use diffuse::DiffuseAreaLight; +pub use distant::DistantLight; +pub use goniometric::GoniometricLight; +pub use infinite::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight}; +pub use point::PointLight; +pub use projection::ProjectionLight; diff --git a/shared/src/lights/point.rs b/shared/src/lights/point.rs new file mode 100644 index 0000000..e789e39 --- /dev/null +++ b/shared/src/lights/point.rs @@ -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 { + 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 { + 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, + )) + } +} diff --git a/shared/src/lights/projection.rs b/shared/src/lights/projection.rs new file mode 100644 index 0000000..4fdbd60 --- /dev/null +++ b/shared/src/lights/projection.rs @@ -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 { + 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 { + todo!() + } +} diff --git a/shared/src/lights/sampler.rs b/shared/src/lights/sampler.rs index 03c3023..c88560c 100644 --- a/shared/src/lights/sampler.rs +++ b/shared/src/lights/sampler.rs @@ -1,15 +1,15 @@ -use super::{ - Bounds3f, Float, Light, LightBounds, LightSampleContext, LightTrait, Normal3f, PI, Point3f, - SampledSpectrum, SampledWavelengths, Vector3f, VectorLike, safe_sqrt, square, -}; use crate::core::geometry::primitives::OctahedralVector; +use crate::core::geometry::{Bounds3f, Normal3f, Point3f, Vector3f, VectorLike}; use crate::core::geometry::{DirectionCone, Normal}; use crate::utils::math::{clamp, lerp, sample_discrete}; use std::collections::HashMap; use std::sync::Arc; -use crate::core::pbrt::ONE_MINUS_EPSILON; +use crate::core::light::{LightBounds, LightSampleContext}; +use crate::spectra::{SampledSpectrum, SampledWavelengths}; +use crate::utils::math::{safe_sqrt, square}; use crate::utils::sampling::AliasTable; +use crate::{Float, ONE_MINUS_EPSILON, PI}; use enum_dispatch::enum_dispatch; #[derive(Clone, Copy, Debug, Default)] diff --git a/shared/src/lights/spot.rs b/shared/src/lights/spot.rs new file mode 100644 index 0000000..0581383 --- /dev/null +++ b/shared/src/lights/spot.rs @@ -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 { + 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 { + 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, + )) + } +} diff --git a/shared/src/spectra/cie.rs b/shared/src/spectra/cie.rs index 7b736a8..179ecf6 100644 --- a/shared/src/spectra/cie.rs +++ b/shared/src/spectra/cie.rs @@ -1,5 +1,7 @@ use crate::Float; +pub const CIE_Y_INTEGRAL: Float = 106.856895; + pub const CIE_SAMPLES: usize = 471; pub const CIE_X: [Float; CIE_SAMPLES] = [ 0.0001299000, @@ -1425,6 +1427,112 @@ pub const CIE_Z: [Float; CIE_SAMPLES] = [ 0.000000000000, ]; +const D65_NORM: Float = 10566.864005283874576; + +macro_rules! N { + ($x:literal) => { + ($x as Float) / D65_NORM + }; +} + +pub const CIE_D65: [Float; 95] = [ + N!(46.6383), + N!(49.3637), + N!(52.0891), + N!(51.0323), + N!(49.9755), + N!(52.3118), + N!(54.6482), + N!(68.7015), + N!(82.7549), + N!(87.1204), + N!(91.486), + N!(92.4589), + N!(93.4318), + N!(90.057), + N!(86.6823), + N!(95.7736), + N!(104.865), + N!(110.936), + N!(117.008), + N!(117.41), + N!(117.812), + N!(116.336), + N!(114.861), + N!(115.392), + N!(115.923), + N!(112.367), + N(108.811), + N!(109.082), + N!(109.354), + N!(108.578), + N!(107.802), + N!(106.296), + N!(104.79), + N!(106.239), + N!(107.689), + N!(106.047), + N!(104.405), + N!(104.225), + N!(104.046), + N!(102.023), + N!(100.0), + N!(98.1671), + N!(96.3342), + N!(96.0611), + N!(95.788), + N!(92.2368), + N!(88.6856), + N!(89.3459), + N!(90.0062), + N!(89.8026), + N!(89.5991), + N!(88.6489), + N!(87.6987), + N!(85.4936), + N!(83.2886), + N!(83.4939), + N!(83.6992), + N!(81.863), + N!(80.0268), + N!(80.1207), + N!(80.2146), + N!(81.2462), + N!(82.2778), + N!(80.281), + N!(78.2842), + N!(74.0027), + N!(69.7213), + N!(70.6652), + N!(71.6091), + N!(72.979), + N!(74.349), + N!(67.9765), + N!(61.604), + N!(65.7448), + N!(69.8856), + N!(72.4863), + N!(75.087), + N!(69.3398), + N!(63.5927), + N!(55.0054), + N!(46.4182), + N!(56.6118), + N!(66.8054), + N!(65.0941), + N!(63.3828), + N!(63.8434), + N!(64.304), + N!(61.8779), + N!(59.4519), + N!(55.7054), + N!(51.959), + N!(54.6998), + N!(57.4406), + N!(58.8765), + N!(60.3125), +]; + pub const CIE_LAMBDA: [i32; CIE_SAMPLES] = [ 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, diff --git a/shared/src/spectra/colorspace.rs b/shared/src/spectra/colorspace.rs index 2057ce5..1419a42 100644 --- a/shared/src/spectra/colorspace.rs +++ b/shared/src/spectra/colorspace.rs @@ -1,8 +1,8 @@ -use super::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ}; +use crate::core::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ}; use crate::core::geometry::Point2f; use crate::core::pbrt::Float; -use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, Spectrum}; -use crate::utils::math::SquareMatrix; +use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; +use crate::utils::math::SquareMatrix3f; use once_cell::sync::Lazy; @@ -10,65 +10,32 @@ use std::cmp::{Eq, PartialEq}; use std::error::Error; use std::sync::Arc; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct StandardColorSpaces { + pub srgb: *const RGBColorSpace, + pub dci_p3: *const RGBColorSpace, + pub rec2020: *const RGBColorSpace, + pub aces2065_1: *const RGBColorSpace, +} + +#[repr(C)] #[derive(Debug, Clone)] pub struct RGBColorSpace { pub r: Point2f, pub g: Point2f, pub b: Point2f, pub w: Point2f, - pub illuminant: Spectrum, - pub rgb_to_spectrum_table: Arc, - pub xyz_from_rgb: SquareMatrix, - pub rgb_from_xyz: SquareMatrix, + pub illuminant: DenselySampledSpectrum, + pub rgb_to_spectrum_table: *const RGBToSpectrumTable, + pub xyz_from_rgb: SquareMatrix3f, + pub rgb_from_xyz: SquareMatrix3f, } +unsafe impl Send for RGBColorSpace {} +unsafe impl Sync for RGBColorSpace {} + impl RGBColorSpace { - pub fn new( - r: Point2f, - g: Point2f, - b: Point2f, - illuminant: Spectrum, - rgb_to_spectrum_table: RGBToSpectrumTable, - ) -> Result> { - 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, String> { - match name.to_lowercase().as_str() { - "aces2065-1" => Ok(Self::aces2065_1().clone()), - "rec2020" => Ok(Self::rec2020().clone()), - "dci-p3" => Ok(Self::dci_p3().clone()), - "srgb" => Ok(Self::srgb().clone()), - _ => Err(format!("Color space '{}' not found", name)), - } - } - pub fn to_xyz(&self, rgb: RGB) -> XYZ { self.xyz_from_rgb * rgb } @@ -81,71 +48,13 @@ impl RGBColorSpace { self.rgb_to_spectrum_table.to_polynomial(rgb) } - pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix { + pub fn convert_colorspace(&self, other: &RGBColorSpace) -> SquareMatrix3f { if self == other { - return SquareMatrix::default(); + return SquareMatrix3f::default(); } self.rgb_from_xyz * other.xyz_from_rgb } - - pub fn srgb() -> &'static Arc { - static SRGB_SPACE: Lazy> = 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 { - static DCI_P3: Lazy> = 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 { - static REC2020: Lazy> = 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 { - static ACES: Lazy> = Lazy::new(|| { - let r = Point2f::new(0.7347, 0.2653); - let g = Point2f::new(0.0000, 1.0000); - let b = Point2f::new(0.0001, -0.0770); - // ACES uses D60 - let illuminant = Spectrum::std_illuminant_d65(); - - let table = RGBToSpectrumTable::aces2065_1(); - - Arc::new(RGBColorSpace::new(r, g, b, illuminant, table).unwrap()) - }); - &ACES - } } impl PartialEq for RGBColorSpace { @@ -154,6 +63,6 @@ impl PartialEq for RGBColorSpace { && self.g == other.g && self.b == other.b && self.w == other.w - && Arc::ptr_eq(&self.rgb_to_spectrum_table, &other.rgb_to_spectrum_table) + && self.rgb_to_spectrum_table == other.rgb_to_spectrum_table } } diff --git a/shared/src/spectra/data.rs b/shared/src/spectra/data.rs deleted file mode 100644 index cc4d6a9..0000000 --- a/shared/src/spectra/data.rs +++ /dev/null @@ -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 = (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 = Lazy::new(|| create_cie_spectrum(&CIE_X)); - &X -} - -pub(crate) fn cie_y() -> &'static Spectrum { - static Y: Lazy = Lazy::new(|| create_cie_spectrum(&CIE_Y)); - &Y -} - -pub(crate) fn cie_z() -> &'static Spectrum { - static Z: Lazy = Lazy::new(|| create_cie_spectrum(&CIE_Z)); - &Z -} diff --git a/shared/src/spectra/mod.rs b/shared/src/spectra/mod.rs index 6d80457..0d9b468 100644 --- a/shared/src/spectra/mod.rs +++ b/shared/src/spectra/mod.rs @@ -1,96 +1,13 @@ pub mod cie; -pub mod color; pub mod colorspace; -pub mod data; pub mod rgb; pub mod sampled; pub mod simple; use crate::core::pbrt::Float; -use enum_dispatch::enum_dispatch; -pub use color::{ColorEncoding, RGB, RGBSigmoidPolynomial, XYZ}; pub use colorspace::RGBColorSpace; -pub use data::*; pub use rgb::*; pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN}; pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; pub use simple::*; -// -#[enum_dispatch] -pub trait SpectrumTrait: Copy { - fn evaluate(&self, lambda: Float) -> Float; - fn max_value(&self) -> Float; -} - -// #[cfg(not(target_arch = "spirv"))] -// impl SpectrumTrait for std::sync::Arc { -// 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(_)) - } -} diff --git a/shared/src/spectra/rgb.rs b/shared/src/spectra/rgb.rs index 2218b7e..20801c9 100644 --- a/shared/src/spectra/rgb.rs +++ b/shared/src/spectra/rgb.rs @@ -1,13 +1,16 @@ use super::{ - DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGB, RGBColorSpace, - RGBSigmoidPolynomial, SampledSpectrum, SampledWavelengths, SpectrumTrait, XYZ, + DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGBColorSpace, + SampledSpectrum, SampledWavelengths, }; +use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ}; +use crate::core::spectrum::SpectrumTrait; use crate::Float; +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct RGBAlbedoSpectrum { - rsp: RGBSigmoidPolynomial, + pub rsp: RGBSigmoidPolynomial, } impl RGBAlbedoSpectrum { @@ -16,14 +19,6 @@ impl RGBAlbedoSpectrum { rsp: cs.to_rgb_coeffs(rgb), } } - - pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { - let mut s = SampledSpectrum::default(); - for i in 0..N_SPECTRUM_SAMPLES { - s[i] = self.rsp.evaluate(lambda[i]); - } - s - } } impl SpectrumTrait for RGBAlbedoSpectrum { @@ -36,10 +31,11 @@ impl SpectrumTrait for RGBAlbedoSpectrum { } } -#[derive(Debug, Clone, Copy)] +#[repr(C)] +#[derive(Debug, Default, Clone, Copy)] pub struct UnboundedRGBSpectrum { - scale: Float, - rsp: RGBSigmoidPolynomial, + pub scale: Float, + pub rsp: RGBSigmoidPolynomial, } impl UnboundedRGBSpectrum { @@ -68,38 +64,31 @@ impl SpectrumTrait for UnboundedRGBSpectrum { } } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct RGBIlluminantSpectrum { - scale: Float, - rsp: RGBSigmoidPolynomial, - illuminant: DenselySampledSpectrum, + pub scale: Float, + pub rsp: RGBSigmoidPolynomial, + pub illuminant: DenselySampledSpectrum, } -// impl RGBIlluminantSpectrum { -// pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self { -// let illuminant = &cs.illuminant; -// let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant); -// let m = rgb.max_component_value(); -// let scale = 2. * m; -// let rsp = cs.to_rgb_coeffs(if scale == 1. { -// rgb / scale -// } else { -// RGB::new(0., 0., 0.) -// }); -// Self { -// scale, -// rsp, -// illuminant: Some(Arc::new(densely_sampled)), -// } -// } -// -// pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { -// if self.illuminant.is_none() { -// return SampledSpectrum::new(0.); -// } -// SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i])) -// } -// } +impl RGBIlluminantSpectrum { + pub fn new(cs: &RGBColorSpace, rgb: RGB) -> Self { + let illuminant = &cs.illuminant; + let densely_sampled = DenselySampledSpectrum::from_spectrum(illuminant); + let m = rgb.max_component_value(); + let scale = 2. * m; + let rsp = cs.to_rgb_coeffs(if scale == 1. { + rgb / scale + } else { + RGB::new(0., 0., 0.) + }); + Self { + scale, + rsp, + illuminant, + } + } +} impl SpectrumTrait for RGBIlluminantSpectrum { fn evaluate(&self, lambda: Float) -> Float { @@ -111,6 +100,13 @@ impl SpectrumTrait for RGBIlluminantSpectrum { } } + fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { + if self.illuminant.is_none() { + return SampledSpectrum::new(0.); + } + SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i])) + } + fn max_value(&self) -> Float { match &self.illuminant { Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(), @@ -126,8 +122,8 @@ pub struct RGBSpectrum { #[derive(Debug, Clone, Copy)] pub struct RGBUnboundedSpectrum { - scale: Float, - rsp: RGBSigmoidPolynomial, + pub scale: Float, + pub rsp: RGBSigmoidPolynomial, } impl Default for RGBUnboundedSpectrum { @@ -155,10 +151,6 @@ impl RGBUnboundedSpectrum { Self { scale, rsp } } - - pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { - SampledSpectrum::from_fn(|i| self.scale * self.rsp.evaluate(lambda[i])) - } } impl SpectrumTrait for RGBUnboundedSpectrum { diff --git a/shared/src/spectra/sampled.rs b/shared/src/spectra/sampled.rs index bc74de4..4e912dc 100644 --- a/shared/src/spectra/sampled.rs +++ b/shared/src/spectra/sampled.rs @@ -1,14 +1,13 @@ use crate::core::pbrt::Float; +use crate::core::spectrum::StandardSpectra; use crate::utils::math::{clamp, lerp}; use std::ops::{ Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, }; -use super::{cie_x, cie_y, cie_z}; - pub const CIE_Y_INTEGRAL: Float = 106.856895; -pub const N_SPECTRUM_SAMPLES: usize = 1200; +pub const N_SPECTRUM_SAMPLES: usize = 4; pub const LAMBDA_MIN: i32 = 360; pub const LAMBDA_MAX: i32 = 830; @@ -43,10 +42,13 @@ impl SampledSpectrum { } } - pub fn from_vector(v: Vec) -> Self { + pub fn from_array(v: &[Float]) -> Self { + assert!(N_SPECTRUM_SAMPLES == v.len()); let mut values = [0.0; N_SPECTRUM_SAMPLES]; - let count = v.len().min(N_SPECTRUM_SAMPLES); - values[..count].copy_from_slice(&v[..count]); + for i in 0..N_SPECTRUM_SAMPLES { + values[i] = v[i]; + } + Self { values } } @@ -115,8 +117,8 @@ impl SampledSpectrum { SampledSpectrum::from_fn(|i| if b[i] != 0. { a[i] / b[i] } else { 0. }) } - pub fn y(&self, lambda: &SampledWavelengths) -> Float { - let ys = cie_y().sample(lambda); + pub fn y(&self, lambda: &SampledWavelengths, std: &StandardSpectra) -> Float { + let ys = std.cie_y().sample(lambda); let pdf = lambda.pdf(); SampledSpectrum::safe_div(&(ys * *self), &pdf).average() / CIE_Y_INTEGRAL } @@ -294,8 +296,8 @@ impl Neg for SampledSpectrum { } } -#[derive(Debug, Copy, Clone, PartialEq)] #[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct SampledWavelengths { pub lambda: [Float; N_SPECTRUM_SAMPLES], pub pdf: [Float; N_SPECTRUM_SAMPLES], diff --git a/shared/src/spectra/simple.rs b/shared/src/spectra/simple.rs index 99929cd..d2902bf 100644 --- a/shared/src/spectra/simple.rs +++ b/shared/src/spectra/simple.rs @@ -1,18 +1,16 @@ +use super::cie::*; use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; -use crate::core::cie::*; -use crate::core::pbrt::Float; -use crate::spectra::{ - N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths, Spectrum, SpectrumTrait, -}; -use crate::utils::file::read_float_file; -use std::cmp::Ordering; -use std::collections::HashMap; +use crate::Float; +use crate::core::spectrum::{Spectrum, SpectrumTrait}; +use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; +use core::slice; use std::hash::{Hash, Hasher}; use std::sync::LazyLock; +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct ConstantSpectrum { - c: Float, + pub c: Float, } impl ConstantSpectrum { @@ -31,90 +29,61 @@ impl SpectrumTrait for ConstantSpectrum { } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct DenselySampledSpectrum { - lambda_min: i32, - lambda_max: i32, - values: Vec, + pub lambda_min: i32, + pub lambda_max: i32, + pub values: *const Float, } +unsafe impl Send for DenselySampledSpectrum {} +unsafe impl Sync for DenselySampledSpectrum {} + impl DenselySampledSpectrum { - pub fn new(lambda_min: i32, lambda_max: i32) -> Self { - let n_values = (lambda_max - lambda_min + 1).max(0) as usize; - Self { - lambda_min, - lambda_max, - values: vec![0.0; n_values], + #[inline(always)] + fn as_slice(&self) -> &[Float] { + if self.values.is_null() { + return &[]; } - } - - pub fn from_spectrum(spec: &Spectrum) -> Self { - let lambda_min = LAMBDA_MIN; - let lambda_max = LAMBDA_MAX; - let mut s = Self::new(lambda_min, lambda_max); - if s.values.is_empty() { - return s; - } - for lambda in lambda_min..=lambda_max { - let index = (lambda - lambda_min) as usize; - s.values[index] = spec.evaluate(lambda as Float); - } - s - } - - pub fn from_spectrum_with_range(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self { - let mut s = Self::new(lambda_min, lambda_max); - if s.values.is_empty() { - return s; - } - for lambda in lambda_min..=lambda_max { - let index = (lambda - lambda_min) as usize; - s.values[index] = spec.evaluate(lambda as Float); - } - s - } - - pub fn from_function(f: F, lambda_min: i32, lambda_max: i32) -> Self - where - F: Fn(Float) -> Float, - { - let mut s = Self::new(lambda_min, lambda_max); - if s.values.is_empty() { - return s; - } - for lambda in lambda_min..=lambda_max { - let index = (lambda - lambda_min) as usize; - s.values[index] = f(lambda as Float); - } - s + let len = (self.lambda_max - self.lambda_min + 1).max(0) as usize; + unsafe { slice::from_raw_parts(self.values, len) } } pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { let mut s = SampledSpectrum::default(); for i in 0..N_SPECTRUM_SAMPLES { - let offset = lambda[i].round() as usize - LAMBDA_MIN as usize; - if offset >= self.values.len() { - s[i] = 0.; + let offset = lambda[i].round() as i32 - self.lambda_min; + let len = (self.lambda_max - self.lambda_min + 1) as i32; + + if offset < 0 || offset >= len { + s[i] = 0.0; } else { - s[i] = self.values[offset]; + unsafe { s[i] = *self.values.add(offset as usize) }; } } s } pub fn min_component_value(&self) -> Float { - self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b)) + self.as_slice() + .iter() + .fold(Float::INFINITY, |a, &b| a.min(b)) } pub fn max_component_value(&self) -> Float { - self.values + self.as_slice() .iter() .fold(Float::NEG_INFINITY, |a, &b| a.max(b)) } pub fn average(&self) -> Float { - self.values.iter().sum::() / (N_SPECTRUM_SAMPLES as Float) + let slice = self.as_slice(); + if slice.is_empty() { + return 0.0; + } + slice.iter().sum::() / (slice.len() as Float) } pub fn safe_div(&self, rhs: SampledSpectrum) -> Self { @@ -128,12 +97,6 @@ impl DenselySampledSpectrum { } r } - - pub fn scale(&mut self, factor: Float) { - for v in &mut self.values { - *v *= factor; - } - } } impl PartialEq for DenselySampledSpectrum { @@ -180,66 +143,16 @@ impl SpectrumTrait for DenselySampledSpectrum { } } -#[derive(Debug, Clone)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct PiecewiseLinearSpectrum { - pub lambdas: Vec, - pub values: Vec, + pub lambdas: *const Float, + pub values: *const Float, + pub count: u32, } -impl PiecewiseLinearSpectrum { - pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self { - if data.len() % 2 != 0 { - panic!("Interleaved data must have an even number of elements"); - } - - let mut temp: Vec<(Float, Float)> = - data.chunks(2).map(|chunk| (chunk[0], chunk[1])).collect(); - - temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); - - let (lambdas, values): (Vec, Vec) = 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 { - let vals = match read_float_file(filepath) { - Ok(v) => v, - Err(_) => Vec::new(), - }; - - if vals.is_empty() { - return None; - } - - if vals.len() % 2 == 0 { - return None; - } - - let count = vals.len() / 2; - let mut lambdas = Vec::with_capacity(count); - let mut values = Vec::with_capacity(count); - - for (_, pair) in vals.chunks(2).enumerate() { - let curr_lambda = pair[0]; - let curr_val = pair[1]; - - if let Some(&prev_lambda) = lambdas.last() { - if curr_lambda <= prev_lambda { - return None; - } - } - - lambdas.push(curr_lambda); - values.push(curr_val); - } - - Some(PiecewiseLinearSpectrum { lambdas, values }) - } -} +unsafe impl Send for PiecewiseLinearSpectrum {} +unsafe impl Sync for PiecewiseLinearSpectrum {} impl SpectrumTrait for PiecewiseLinearSpectrum { fn evaluate(&self, lambda: Float) -> Float { @@ -274,10 +187,11 @@ impl SpectrumTrait for PiecewiseLinearSpectrum { } } +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct BlackbodySpectrum { - temperature: Float, - normalization_factor: Float, + pub temperature: Float, + pub normalization_factor: Float, } // Planck's Law @@ -331,150 +245,3 @@ impl SpectrumTrait for BlackbodySpectrum { Self::planck_law(lambda_max, self.temperature) } } - -pub static NAMED_SPECTRA: LazyLock> = 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 { - NAMED_SPECTRA.get(name).cloned() -} diff --git a/shared/src/textures/mix.rs b/shared/src/textures/mix.rs index 022bc8c..4e7835a 100644 --- a/shared/src/textures/mix.rs +++ b/shared/src/textures/mix.rs @@ -1,14 +1,33 @@ +use crate::core::geometry::Vector3f; +use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; +use crate::utils::Ptr; + #[repr(C)] -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub struct GPUFloatMixTexture { - tex1: GPUFloatTexture, - tex2: GPUFloatTexture, + pub tex1: Ptr, + pub tex2: Ptr, } #[repr(C)] -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub struct GPUFloatDirectionMixTexture { - tex1: GPUFloatTexture, - tex2: GPUFloatTexture, - dir: Vector3f, + pub tex1: Ptr, + pub tex2: Ptr, + pub dir: Vector3f, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct GPUSpectrumMixTexture { + pub tex1: Ptr, + pub tex2: Ptr, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct GPUSpectrumDirectionMixTexture { + pub tex1: Ptr, + pub tex2: Ptr, + pub dir: Vector3f, } diff --git a/shared/src/textures/mod.rs b/shared/src/textures/mod.rs index 78218f5..7e369ab 100644 --- a/shared/src/textures/mod.rs +++ b/shared/src/textures/mod.rs @@ -7,6 +7,7 @@ pub mod image; pub mod marble; pub mod mix; pub mod ptex; +pub mod scaled; pub mod windy; pub mod wrinkled; @@ -19,5 +20,6 @@ pub use image::*; pub use marble::*; pub use mix::*; pub use ptex::*; +pub use scaled::*; pub use windy::*; pub use wrinkled::*; diff --git a/shared/src/textures/scaled.rs b/shared/src/textures/scaled.rs new file mode 100644 index 0000000..b531f02 --- /dev/null +++ b/shared/src/textures/scaled.rs @@ -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, + scale: Ptr, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct GPUSpectrumScaledTexture { + tex: Ptr, + scale: Ptr, +} diff --git a/shared/src/utils/containers.rs b/shared/src/utils/containers.rs index cbd8bae..be1815c 100644 --- a/shared/src/utils/containers.rs +++ b/shared/src/utils/containers.rs @@ -180,31 +180,40 @@ impl IndexMut<(i32, i32)> for Array2D { #[derive(Debug, Clone)] pub struct SampledGrid { - values: Vec, - nx: i32, - ny: i32, - nz: i32, + pub values: *const T, + pub nx: i32, + pub ny: i32, + pub nz: i32, } +unsafe impl Sync for SampledGrid {} +unsafe impl Send for SampledGrid {} + impl SampledGrid { - pub fn new(values: Vec, nx: i32, ny: i32, nz: i32) -> Self { - assert_eq!( - values.len(), - (nx * ny * nz) as usize, - "Grid dimensions do not match data size" - ); - Self { values, nx, ny, nz } + #[cfg(not(target_os = "cuda"))] + pub fn new(slice: &[T], nx: i32, ny: i32, nz: i32) -> Self { + assert_eq!(slice.len(), (nx * ny * nz) as usize); + Self { + values: slice.as_ptr(), + nx, + ny, + nz, + } } pub fn empty() -> Self { Self { - values: Vec::new(), + values: core::ptr::null(), nx: 0, ny: 0, nz: 0, } } + pub fn is_valid(&self) -> bool { + !self.values.is_null() && self.nx > 0 && self.ny > 0 && self.nz > 0 + } + pub fn bytes_allocated(&self) -> usize { self.values.len() * std::mem::size_of::() } @@ -212,9 +221,11 @@ impl SampledGrid { pub fn x_size(&self) -> i32 { self.nx } + pub fn y_size(&self) -> i32 { self.ny } + pub fn z_size(&self) -> i32 { self.nz } @@ -224,7 +235,11 @@ impl SampledGrid { F: Fn(&T) -> U, U: Default, { - let sample_bounds = Bounds3i::from_points( + if !self.is_valid() { + return U::default(); + } + + let sample_bounds = Bounds3i::new( Point3i::new(0, 0, 0), Point3i::new(self.nx, self.ny, self.nz), ); @@ -234,7 +249,10 @@ impl SampledGrid { } let idx = (p.z() * self.ny + p.y()) * self.nx + p.x(); - convert(&self.values[idx as usize]) + unsafe { + let val = &*self.values.add(idx as usize); + convert(val) + } } pub fn lookup_int(&self, p: Point3i) -> T @@ -249,6 +267,10 @@ impl SampledGrid { F: Fn(&T) -> U + Copy, U: Interpolatable + Default, { + if !self.is_valid() { + return U::default(); + } + let p_samples = Point3f::new( p.x() * self.nx as Float - 0.5, p.y() * self.ny as Float - 0.5, @@ -298,6 +320,10 @@ impl SampledGrid { F: Fn(&T) -> U, U: PartialOrd + Default, { + if !self.is_valid() { + return U::default(); + } + let ps = [ Point3f::new( bounds.p_min.x() * self.nx as Float - 0.5, @@ -318,7 +344,6 @@ impl SampledGrid { self.nz - 1, )); - // Initialize with the first voxel let mut max_value = self.lookup_int_convert(pi_min, &convert); for z in pi_min.z()..=pi_max.z() { @@ -342,30 +367,3 @@ impl SampledGrid { self.max_value_convert(bounds, |v| v.clone()) } } - -pub struct InternCache { - cache: Mutex>>, -} - -impl InternCache -where - T: Eq + Hash + Clone, -{ - pub fn new() -> Self { - Self { - cache: Mutex::new(HashSet::new()), - } - } - - pub fn lookup(&self, value: T) -> Arc { - 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 - } -} diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index 151579d..cca9f9e 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -6,7 +6,6 @@ use crate::utils::hash::{hash_buffer, mix_bits}; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; use num_traits::{Float as NumFloat, Num, One, Signed, Zero}; -use rayon::prelude::*; use std::error::Error; use std::fmt::{self, Display}; use std::iter::{Product, Sum}; @@ -64,16 +63,13 @@ where } #[inline] -pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option { - if coeffs.is_empty() { - return None; - } - +pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float { + assert!(!coeffs.is_empty()); let mut result = coeffs[0]; for &c in &coeffs[1..] { result = t.mul_add(result, c); } - Some(result) + result } #[inline] @@ -913,6 +909,7 @@ pub fn multiply_generator(c: &[u32], mut a: u32) -> u32 { v } +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct NoRandomizer; impl NoRandomizer { @@ -921,6 +918,7 @@ impl NoRandomizer { } } +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct BinaryPermuteScrambler { pub permutation: u32, @@ -937,6 +935,7 @@ impl BinaryPermuteScrambler { } } +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct FastOwenScrambler { pub seed: u32, @@ -970,6 +969,7 @@ impl FastOwenScrambler { } } +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct OwenScrambler { pub seed: u32, @@ -1096,9 +1096,10 @@ pub fn sobol_interval_to_index(m: u32, frame: u64, p: Point2i) -> u64 { } // MATRIX STUFF (TEST THOROUGHLY) +#[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Matrix { - m: [[T; C]; R], + pub m: [[T; C]; R], } impl Matrix { @@ -1261,6 +1262,7 @@ where } pub type SquareMatrix = Matrix; +pub type SquareMatrix3f = SquareMatrix; impl SquareMatrix { pub fn identity() -> Self diff --git a/shared/src/utils/mod.rs b/shared/src/utils/mod.rs index e30050c..3609ec9 100644 --- a/shared/src/utils/mod.rs +++ b/shared/src/utils/mod.rs @@ -7,6 +7,7 @@ pub mod interval; pub mod math; pub mod mesh; pub mod noise; +pub mod ptr; pub mod quaternion; pub mod rng; pub mod sampling; @@ -14,6 +15,7 @@ pub mod sobol; pub mod splines; pub mod transform; +pub use ptr::Ptr; pub use transform::{AnimatedTransform, Transform, TransformGeneric}; #[inline] diff --git a/shared/src/utils/ptr.rs b/shared/src/utils/ptr.rs new file mode 100644 index 0000000..b8a51ef --- /dev/null +++ b/shared/src/utils/ptr.rs @@ -0,0 +1,49 @@ +use core::marker::PhantomData; + +#[repr(C)] +#[derive(Debug)] +pub struct Ptr { + offset: isize, + _phantom: PhantomData, +} + +impl Clone for Ptr { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Ptr {} + +impl Ptr { + 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, + } + } +} diff --git a/shared/src/utils/quaternion.rs b/shared/src/utils/quaternion.rs index 375e473..3811fb9 100644 --- a/shared/src/utils/quaternion.rs +++ b/shared/src/utils/quaternion.rs @@ -1,11 +1,11 @@ -use std::f32::consts::PI; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use std::ops::{Index, IndexMut}; use crate::core::geometry::{Vector3f, VectorLike}; -use crate::core::pbrt::Float; use crate::utils::math::{safe_asin, sinx_over_x}; +use crate::{Float, PI}; +#[repr(C)] #[derive(Copy, Clone, Debug, PartialEq)] pub struct Quaternion { pub v: Vector3f, diff --git a/shared/src/utils/transform.rs b/shared/src/utils/transform.rs index 8f2959c..80bdfdf 100644 --- a/shared/src/utils/transform.rs +++ b/shared/src/utils/transform.rs @@ -5,8 +5,9 @@ use std::iter::{Product, Sum}; use std::ops::{Add, Div, Index, IndexMut, Mul}; use std::sync::Arc; -use super::math::{radians, safe_acos, SquareMatrix}; +use super::math::{SquareMatrix, radians, safe_acos}; use super::quaternion::Quaternion; +use crate::core::color::{RGB, XYZ}; use crate::core::geometry::{ Bounds3f, Normal, Normal3f, Point, Point3f, Point3fi, Ray, Vector, Vector3f, Vector3fi, VectorLike, @@ -14,10 +15,10 @@ use crate::core::geometry::{ use crate::core::interaction::{ Interaction, InteractionData, InteractionTrait, MediumInteraction, SurfaceInteraction, }; -use crate::core::pbrt::{gamma, Float}; -use crate::spectra::{RGB, XYZ}; +use crate::core::pbrt::{Float, gamma}; use crate::utils::error::InversionError; +#[repr(C)] #[derive(Debug, Copy, Clone)] pub struct TransformGeneric { m: SquareMatrix, @@ -754,6 +755,7 @@ impl From for TransformGeneric { } } +#[repr(C)] #[derive(Default, Debug, Copy, Clone)] pub struct DerivativeTerm { kc: Float, @@ -777,7 +779,8 @@ impl DerivativeTerm { } } -#[derive(Debug, Default, Clone)] +#[repr(C)] +#[derive(Debug, Copy, Default, Clone)] pub struct AnimatedTransform { pub start_transform: Transform, pub end_transform: Transform, diff --git a/src/core/camera.rs b/src/core/camera.rs new file mode 100644 index 0000000..e48b660 --- /dev/null +++ b/src/core/camera.rs @@ -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, + pub medium: Arc, +} + +impl CameraBaseParameters { + pub fn new( + camera_transform: &CameraTransform, + film: Arc, + medium: Arc, + 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, + loc: &FileLoc, + ) -> Result; +} + +impl CameraFactory for Camera { + fn create( + name: &str, + params: &ParameterDictionary, + camera_transform: &CameraTransform, + medium: Medium, + film: Arc, + loc: &FileLoc, + ) -> Result { + 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 = 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)), + } + } +} diff --git a/src/core/color.rs b/src/core/color.rs new file mode 100644 index 0000000..908c0cb --- /dev/null +++ b/src/core/color.rs @@ -0,0 +1,47 @@ +use shared::Float; +use shared::core::color::{Coeffs, RES, RGBToSpectrumTable}; +use shared::spectra::RGBSigmoidPolynomial; + +pub struct RGBToSpectrumTableData { + _z_nodes: Vec, + _coeffs: Vec, + + 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, coeffs: Vec) -> 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], +]); diff --git a/src/core/film.rs b/src/core/film.rs index a4d9c67..746b591 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -88,7 +88,7 @@ impl FilmBaseHost for FilmBase { fn create( params: &ParameterDictionary, filter: Filter, - sensor: Option, + sensor: Option<&PixelSensor>, loc: &FileLoc, ) -> Self { let x_res = params.get_one_int("xresolution", 1280); diff --git a/src/core/mod.rs b/src/core/mod.rs index fb499ad..ba96e37 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,4 +1,7 @@ +pub mod camera; +pub mod color; pub mod film; pub mod filter; pub mod scene; +pub mod spectrum; pub mod texture; diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs new file mode 100644 index 0000000..d24e749 --- /dev/null +++ b/src/core/spectrum.rs @@ -0,0 +1,8 @@ +use crate::utils::containers::InternCache; + +static SPECTRUM_CACHE: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +fn get_spectrum_cache() -> &'static InternCache { + SPECTRUM_CACHE.get_or_init(InternCache::new) +} diff --git a/src/core/texture.rs b/src/core/texture.rs index cccca3b..b4695fa 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -250,41 +250,3 @@ struct TexInfo { wrap_mode: WrapMode, encoding: ColorEncoding, } - -pub trait TextureEvaluator: Send + Sync { - fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float; - fn evaluate_spectrum( - &self, - tex: &SpectrumTexture, - ctx: &TextureEvalContext, - lambda: &SampledWavelengths, - ) -> SampledSpectrum; - - fn can_evaluate(&self, _ftex: &[&FloatTexture], _stex: &[&SpectrumTexture]) -> bool; -} - -#[derive(Copy, Clone, Default)] -pub struct UniversalTextureEvaluator; - -impl TextureEvaluator for UniversalTextureEvaluator { - fn evaluate_float(&self, tex: &FloatTexture, ctx: &TextureEvalContext) -> Float { - tex.evaluate(ctx) - } - - fn evaluate_spectrum( - &self, - tex: &SpectrumTexture, - ctx: &TextureEvalContext, - lambda: &SampledWavelengths, - ) -> SampledSpectrum { - tex.evaluate(ctx, lambda) - } - - fn can_evaluate( - &self, - _float_textures: &[&FloatTexture], - _spectrum_textures: &[&SpectrumTexture], - ) -> bool { - true - } -} diff --git a/src/lib.rs b/src/lib.rs index 162b9b4..4f9bee8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod core; pub mod lights; +pub mod spectra; pub mod textures; pub mod utils; diff --git a/src/spectra/colorspace.rs b/src/spectra/colorspace.rs new file mode 100644 index 0000000..d47212f --- /dev/null +++ b/src/spectra/colorspace.rs @@ -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, 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)), + } +} diff --git a/src/spectra/data.rs b/src/spectra/data.rs new file mode 100644 index 0000000..ebfbd56 --- /dev/null +++ b/src/spectra/data.rs @@ -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> = 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 { + let buffer = NAMED_SPECTRA_DATA.get(name)?; + Some(Spectrum::PiecewiseLinear(buffer.view)) +} diff --git a/src/spectra/dense.rs b/src/spectra/dense.rs new file mode 100644 index 0000000..6359d82 --- /dev/null +++ b/src/spectra/dense.rs @@ -0,0 +1,112 @@ +use crate::core::pbrt::Float; +use shared::spectra::{DenselySampledSpectrum, SampledSpectrum}; + +pub struct DenselySampledSpectrumBuffer { + pub view: DenselySampledSpectrum, + _storage: Vec, +} + +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) -> 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, 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 = (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; + } + } +} diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs new file mode 100644 index 0000000..d6414fe --- /dev/null +++ b/src/spectra/mod.rs @@ -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 = Lazy::new(|| data::create_cie_buffer(&CIE_X)); +static CIE_Y_DATA: Lazy = Lazy::new(|| data::create_cie_buffer(&CIE_Y)); +static CIE_Z_DATA: Lazy = Lazy::new(|| data::create_cie_buffer(&CIE_Z)); +static CIE_D65_DATA: Lazy = 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 = 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 = 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> = 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> = 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, + } +} diff --git a/src/spectra/piecewise.rs b/src/spectra/piecewise.rs new file mode 100644 index 0000000..ddae525 --- /dev/null +++ b/src/spectra/piecewise.rs @@ -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, + _values: Vec, +} + +impl Deref for PiecewiseLinearSpectrumBuffer { + type Target = PiecewiseLinearSpectrum; + fn deref(&self) -> &Self::Target { + &self.view + } +} + +impl PiecewiseLinearSpectrumBuffer { + pub fn new(lambdas: Vec, values: Vec) -> 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, Vec) = temp.into_iter().unzip(); + + // (Normalization logic usually goes here) + + Self::new(lambdas, values) + } + + pub fn read(filepath: &str) -> Option { + 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)) + } +} diff --git a/src/textures/mod.rs b/src/textures/mod.rs index 6ff4be0..16afa38 100644 --- a/src/textures/mod.rs +++ b/src/textures/mod.rs @@ -1,25 +1,9 @@ -pub mod bilerp; -pub mod checkerboard; -pub mod constant; -pub mod dots; -pub mod fbm; pub mod image; -pub mod marble; pub mod mix; pub mod ptex; -pub mod scale; -pub mod windy; -pub mod wrinkled; +pub mod scaled; -pub use bilerp::*; -pub use checkerboard::*; -pub use constant::*; -pub use dots::*; -pub use fbm::*; pub use image::*; -pub use marble::*; pub use mix::*; pub use ptex::*; -pub use scale::*; -pub use windy::*; -pub use wrinkled::*; +pub use scaled::*; diff --git a/src/textures/scale.rs b/src/textures/scaled.rs similarity index 100% rename from src/textures/scale.rs rename to src/textures/scaled.rs diff --git a/src/utils/containers.rs b/src/utils/containers.rs new file mode 100644 index 0000000..75d7bc4 --- /dev/null +++ b/src/utils/containers.rs @@ -0,0 +1,26 @@ +pub struct InternCache { + cache: Mutex>>, +} + +impl InternCache +where + T: Eq + Hash + Clone, +{ + pub fn new() -> Self { + Self { + cache: Mutex::new(HashSet::new()), + } + } + + pub fn lookup(&self, value: T) -> Arc { + 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 + } +} diff --git a/src/utils/file.rs b/src/utils/file.rs index 4577327..7e3e91e 100644 --- a/src/utils/file.rs +++ b/src/utils/file.rs @@ -16,6 +16,31 @@ fn set_search_directory(filename: &str) { let _ = SEARCH_DIRECTORY.set(dir); } +pub fn resolve_filename(filename: &str) -> String { + let search_directory = SEARCH_DIRECTORY + .get() + .map(|p| p.as_path()) + .unwrap_or(Path::new("")); + + if search_directory.as_os_str().is_empty() + || filename.is_empty() + || Path::new(filename).is_absolute() + { + return filename.to_string(); + } + + let filepath = search_directory.join(filename); + if filepath.exists() { + filepath + .canonicalize() + .unwrap_or_else(|_| filepath.to_path_buf()) + .to_string_lossy() + .to_string() + } else { + filename.to_string() + } +} + pub fn read_float_file(filename: &str) -> io::Result> { let content = fs::read_to_string(filename)?; @@ -45,28 +70,3 @@ pub fn read_float_file(filename: &str) -> io::Result> { 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() - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 72750af..dd88323 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod containers; pub mod error; pub mod file; pub mod io;