use crate::core::camera::CameraTransform; use crate::core::filter::Filter; use crate::core::geometry::{ Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, Vector2i, Vector3f, }; use crate::core::interaction::SurfaceInteraction; use crate::core::pbrt::Float; 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, }; use crate::utils::AtomicFloat; use crate::utils::containers::Array2D; 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)] pub struct RGBFilm { pub base: FilmBase, pub max_component_value: Float, pub write_fp16: bool, pub filter_integral: Float, pub output_rgbf_from_sensor_rgb: SquareMatrix, pub pixels: Arc>, } #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct RGBPixel { rgb_sum: [AtomicFloat; 3], weight_sum: AtomicFloat, rgb_splat: [AtomicFloat; 3], } #[cfg(not(target_os = "cuda"))] impl RGBFilm { pub fn new( base: FilmBase, colorspace: &RGBColorSpace, max_component_value: Float, write_fp16: bool, ) -> Self { let sensor_ptr = base.sensor; if sensor_ptr.is_null() { panic!("Film must have a sensor"); } let sensor = unsafe { &*self.sensor }; 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; let width = base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x(); let height = base.pixel_bounds.p_max.y() - base.pixel_bounds.p_min.y(); let count = (width * height) as usize; let mut pixel_vec = Vec::with_capacity(count); for _ in 0..count { pixel_vec.push(RGBPixel::default()); } let pixels_array = Array2D::new(base.pixel_bounds); Self { base, max_component_value, write_fp16, filter_integral, output_rgbf_from_sensor_rgb, pixels: Arc::new(pixels_array), } } } impl RGBFilm { pub fn base(&self) -> &FilmBase { &self.base } pub fn base_mut(&mut self) -> &mut FilmBase { &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 fn add_sample( &self, p_film: Point2i, l: SampledSpectrum, lambda: &SampledWavelengths, _vi: Option<&VisibleSurface>, weight: Float, ) { 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 { rgb *= self.max_component_value / m; } let pixel = &self.pixels[p_film]; for c in 0..3 { pixel.rgb_sum[c].add((weight * rgb[c]) as f64); } pixel.weight_sum.add(weight as f64); } pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { 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 { rgb *= self.max_component_value / m; } let p_discrete = p + Vector2f::new(0.5, 0.5); let radius = self.get_filter().radius(); let splat_bounds = Bounds2i::from_points( (p_discrete - radius).floor(), (p_discrete + radius).floor() + Vector2i::new(1, 1), ); let splat_intersect = splat_bounds.union(self.pixel_bounds()); for pi in &splat_intersect { let pi_f: Point2f = (*pi).into(); let wt = self .get_filter() .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); if wt != 0. { let pixel = &self.pixels[*pi]; for i in 0..3 { pixel.rgb_splat[i].add((wt * rgb[i]) as f64); } } } } pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB { let pixel = unsafe { &self.pixels.get(p.x(), p.y())[p] }; let mut rgb = RGB::new( pixel.rgb_sum[0].load() as Float, pixel.rgb_sum[1].load() as Float, pixel.rgb_sum[2].load() as Float, ); let weight_sum = pixel.weight_sum.load(); if weight_sum != 0. { rgb /= weight_sum as Float } if let Some(splat) = splat_scale { for c in 0..3 { let splat_val = pixel.rgb_splat[c].load(); rgb[c] += splat * splat_val as Float / self.filter_integral; } } else { for c in 0..3 { let splat_val = pixel.rgb_splat[c].load(); rgb[c] += splat_val as Float / self.filter_integral; } } self.output_rgbf_from_sensor_rgb.mul_rgb(rgb) } pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { let sensor = unsafe { self.get_sensor() }; let mut sensor_rgb = sensor.to_sensor_rgb(l, lambda); self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) } fn uses_visible_surface(&self) -> bool { false } } #[derive(Debug, Clone, Copy, Default)] struct GBufferPixel { pub rgb_sum: [AtomicFloat; 3], pub weight_sum: AtomicFloat, pub g_bugger_weight_sum: AtomicFloat, pub rgb_splat: [AtomicFloat; 3], pub p_sum: Point3f, pub dz_dx_sum: AtomicFloat, pub dz_dy_sum: Float, pub n_sum: Normal3f, pub ns_sum: Normal3f, pub uv_sum: Point2f, pub rgb_albedo_sum: [AtomicFloat; 3], pub rgb_variance: VarianceEstimator, } #[derive(Clone, Debug)] pub struct GBufferFilm { pub base: FilmBase, output_from_render: AnimatedTransform, apply_inverse: bool, pixels: Array2D, colorspace: RGBColorSpace, max_component_value: Float, write_fp16: bool, filter_integral: Float, output_rgbf_from_sensor_rgb: SquareMatrix, } #[cfg(not(target_os = "cuda"))] impl GBufferFilm { pub fn new( base: &FilmBase, output_from_render: &AnimatedTransform, apply_inverse: bool, colorspace: &RGBColorSpace, max_component_value: Float, write_fp16: bool, ) -> Self { assert!(!base.pixel_bounds.is_empty()); let sensor_ptr = base.sensor; if sensor_ptr.is_null() { panic!("Film must have a sensor"); } let sensor = unsafe { &*sensor_ptr }; let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb; let filter_integral = base.filter.integral(); let pixels = Array2D::new(base.pixel_bounds); Self { base: base.clone(), output_from_render: output_from_render.clone(), apply_inverse, pixels, colorspace: colorspace.clone(), max_component_value, write_fp16, filter_integral, output_rgbf_from_sensor_rgb, } } } impl GBufferFilm { pub fn base(&self) -> &FilmBase { &self.base } pub fn base_mut(&mut self) -> &mut FilmBase { &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 { #[cfg(not(target_os = "cuda"))] { if self.sensor.is_null() { panic!("FilmBase: PixelSensor pointer is null"); } } &*self.sensor } pub fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) { let sensor = unsafe { self.get_pixel_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 { rgb *= self.max_component_value / m; } let p_discrete = p + Vector2f::new(0.5, 0.5); let radius = self.get_filter().radius(); let splat_bounds = Bounds2i::from_points( (p_discrete - radius).floor(), (p_discrete + radius).floor() + Vector2i::new(1, 1), ); let splat_intersect = splat_bounds.union(self.pixel_bounds()); for pi in &splat_intersect { let pi_f: Point2f = (*pi).into(); let wt = self .get_filter() .evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into()); if wt != 0. { let pixel = &self.pixels[*pi]; for i in 0..3 { pixel.rgb_splat[i].add((wt * rgb[i]) as f64); } } } } pub fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { let sensor = unsafe { self.get_pixel_sensor() }; let sensor_rgb = sensor.to_sensor_rgb(l, lambda); self.output_rgbf_from_sensor_rgb.mul_rgb(sensor_rgb) } pub fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB { let pixel = unsafe { &self.pixels.get(p.x(), p.y()) }; let mut rgb = RGB::new( pixel.rgb_sum[0].load() as Float, pixel.rgb_sum[1].load() as Float, pixel.rgb_sum[2].load() as Float, ); let weight_sum = pixel.weight_sum.load(); if weight_sum != 0. { rgb /= weight_sum as Float } if let Some(splat) = splat_scale { for c in 0..3 { let splat_val = pixel.rgb_splat[c].load(); rgb[c] += splat * splat_val as Float / self.filter_integral; } } else { for c in 0..3 { let splat_val = pixel.rgb_splat[c].load(); rgb[c] += splat_val as Float / self.filter_integral; } } self.output_rgbf_from_sensor_rgb.mul_rgb(rgb) } pub fn uses_visible_surface(&self) -> bool { true } } #[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct SpectralPixel { pub rgb_sum: [AtomicFloat; 3], pub rgb_weigh_sum: AtomicFloat, pub rgb_splat: [AtomicFloat; 3], pub bucket_offset: usize, } pub struct SpectralPixelView<'a> { pub metadata: &'a SpectralPixel, pub bucket_sums: &'a [f64], pub weight_sums: &'a [f64], pub bucket_splats: &'a [AtomicFloat], } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct SpectralFilm { pub base: FilmBase, pub colorspace: RGBColorSpace, pub lambda_min: Float, pub lambda_max: Float, pub n_buckets: usize, pub max_component_value: Float, pub write_fp16: bool, pub filter_integral: Float, pub pixels: Array2D, pub output_rgbf_from_sensor_rgb: SquareMatrix, pub bucket_sums: Vec, pub weight_sums: Vec, pub bucket_splats: Vec, } #[cfg(not(target_os = "cuda"))] impl SpectralFilm { pub fn new( base: &FilmBase, lambda_min: Float, lambda_max: Float, n_buckets: usize, colorspace: &RGBColorSpace, max_component_value: Float, write_fp16: bool, ) -> Self { assert!(!base.pixel_bounds.is_empty()); let sensor_ptr = base.sensor; if sensor_ptr.is_null() { panic!("Film must have a sensor"); } let sensor = unsafe { &*sensor_ptr }; let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb; let n_pixels = base.pixel_bounds.area() as usize; let total_bucket_count = n_pixels * n_buckets; let bucket_sums = vec![0.0; total_bucket_count]; let weight_sums = vec![0.0; total_bucket_count]; let filter_integral = base.filter.integral(); let bucket_splats: Vec = (0..total_bucket_count) .map(|_| AtomicFloat::new(0.0)) .collect(); let mut pixels = Array2D::::new(base.pixel_bounds); for i in 0..n_pixels { pixels.get_linear_mut(i).bucket_offset = i * n_buckets; } Self { base: base.clone(), lambda_min, lambda_max, n_buckets, pixels, bucket_sums, weight_sums, bucket_splats, colorspace: colorspace.clone(), max_component_value, write_fp16, filter_integral, output_rgbf_from_sensor_rgb, } } } impl SpectralFilm { pub fn base(&self) -> &FilmBase { &self.base } pub fn base_mut(&mut self) -> &mut FilmBase { &mut self.base } fn uses_visible_surface(&self) -> bool { true } pub fn get_pixel_view(&self, p: Point2i) -> SpectralPixelView { let metadata = unsafe { &self.pixels.get(p.x(), p.y()) }; let start = metadata.bucket_offset; let end = start + self.n_buckets; SpectralPixelView { metadata, bucket_sums: &self.bucket_sums[start..end], weight_sums: &self.weight_sums[start..end], bucket_splats: &self.bucket_splats[start..end], } } } #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct PixelSensor { pub xyz_from_sensor_rgb: SquareMatrix, pub r_bar: DenselySampledSpectrum, pub g_bar: DenselySampledSpectrum, pub b_bar: DenselySampledSpectrum, pub imaging_ratio: Float, } #[cfg(not(target_os = "cuda"))] impl PixelSensor { const N_SWATCH_REFLECTANCES: usize = 24; pub fn new( r: Spectrum, g: Spectrum, b: Spectrum, output_colorspace: RGBColorSpace, sensor_illum: Option>, imaging_ratio: Float, swatches: &[Spectrum; 24], ) -> Result> { // As seen in usages of this constructos, sensor_illum can be null // Going with the colorspace's own illuminant, but this might not be the right choice // TODO: Test this let illum: &Spectrum = match &sensor_illum { Some(arc_illum) => &**arc_illum, None => &output_colorspace.illuminant, }; 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 swatches = Self::get_swatches(); for i in 0..N_SWATCH_REFLECTANCES { let rgb = Self::project_reflectance::( &swatches[i], illum, &Spectrum::DenselySampled(r_bar.clone()), &Spectrum::DenselySampled(g_bar.clone()), &Spectrum::DenselySampled(b_bar.clone()), ); for c in 0..3 { rgb_camera[i][c] = rgb[c]; } } let mut xyz_output = [[0.; 3]; 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 { let s = swatches[i].clone(); let xyz = Self::project_reflectance::( &s, &output_colorspace.illuminant, cie_x(), cie_y(), cie_z(), ) * (sensor_white_y / sensor_white_g); for c in 0..3 { xyz_output[i][c] = xyz[c]; } } let xyz_from_sensor_rgb = linear_least_squares(rgb_camera, xyz_output)?; Ok(Self { xyz_from_sensor_rgb, r_bar, g_bar, b_bar, imaging_ratio, }) } pub fn new_with_white_balance( output_colorspace: &RGBColorSpace, sensor_illum: Option>, imaging_ratio: Float, ) -> Self { let r_bar = DenselySampledSpectrum::from_spectrum(cie_x()); let g_bar = DenselySampledSpectrum::from_spectrum(cie_y()); let b_bar = DenselySampledSpectrum::from_spectrum(cie_z()); let xyz_from_sensor_rgb: SquareMatrix; if let Some(illum) = sensor_illum { let source_white = illum.to_xyz().xy(); let target_white = output_colorspace.w; xyz_from_sensor_rgb = white_balance(source_white, target_white); } else { xyz_from_sensor_rgb = SquareMatrix::::default(); } Self { xyz_from_sensor_rgb, r_bar, g_bar, b_bar, imaging_ratio, } } pub fn project_reflectance( refl: &Spectrum, illum: &Spectrum, b1: &Spectrum, b2: &Spectrum, b3: &Spectrum, ) -> T { let mut result = [0.; 3]; let mut g_integral = 0.; for lambda_ind in LAMBDA_MIN..=LAMBDA_MAX { let lambda = lambda_ind as Float; let illum_val = illum.evaluate(lambda); g_integral += b2.evaluate(lambda) * illum_val; let refl_illum = refl.evaluate(lambda) * illum_val; result[0] += b1.evaluate(lambda) * refl_illum; result[1] += b2.evaluate(lambda) * refl_illum; result[2] += b3.evaluate(lambda) * refl_illum; } if g_integral > 0. { let inv_g = 1. / g_integral; result[0] *= inv_g; result[1] *= inv_g; result[2] *= inv_g; } T::from_triplet(result[0], result[1], result[2]) } } impl PixelSensor { pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB { let l_norm = SampledSpectrum::safe_div(&l, &lambda.pdf()); self.imaging_ratio * RGB::new( (self.r_bar.sample(lambda) * l_norm).average(), (self.g_bar.sample(lambda) * l_norm).average(), (self.b_bar.sample(lambda) * l_norm).average(), ) } } #[repr(C)] #[derive(Default, Copy, Clone)] pub struct VisibleSurface { pub p: Point3f, pub n: Normal3f, pub ns: Normal3f, pub uv: Point2f, pub time: Float, pub dpdx: Vector3f, pub dpdy: Vector3f, pub albedo: SampledSpectrum, pub set: bool, } impl VisibleSurface { pub fn new( _si: &SurfaceInteraction, albedo: &SampledSpectrum, _lambda: &SampledWavelengths, ) -> Self { VisibleSurface { albedo: *albedo, ..Default::default() } } } #[repr(C)] #[derive(Default, Copy, Clone)] pub struct FilmBase { pub full_resolution: Point2i, pub pixel_bounds: Bounds2i, pub filter: Filter, pub diagonal: Float, pub sensor: *const PixelSensor, } #[repr(C)] #[derive(Debug, Copy, Clone)] pub enum Film { RGB(RGBFilm), GBuffer(GBufferFilm), Spectral(SpectralFilm), } impl Film { fn base(&self) -> &FilmBase { match self { Film::RGB(f) => f.base(), Film::GBuffer(f) => f.base(), Film::Spectral(f) => f.base(), } } pub fn base(&mut self) -> &mut FilmBase { match self { Film::RGB(f) => f.base_mut(), Film::GBuffer(f) => f.base_mut(), Film::Spectral(f) => f.base_mut(), } } pub fn add_sample( &self, p_film: Point2i, l: SampledSpectrum, lambda: &SampledWavelengths, visible_surface: Option<&VisibleSurface>, weight: Float, ) { match self { Film::RGB(f) => f.add_sample(p_film, l, lambda, visible_surface, weight), Film::GBuffer(f) => f.add_sample(p_film, l, lambda, visible_surface, weight), Film::Spectral(f) => f.add_sample(p_film, l, lambda, visible_surface, weight), } } pub fn add_splat(&mut self, p: Point2f, v: SampledSpectrum, lambda: &SampledWavelengths) { match self { Film::RGB(f) => f.add_splat(p, v, lambda), Film::GBuffer(f) => f.add_splat(p, v, lambda), Film::Spectral(f) => f.add_splat(p, v, lambda), } } pub fn sample_wavelengths(&self, u: Float) -> SampledWavelengths { SampledWavelengths::sample_visible(u) } pub fn uses_visible_surface(&self) -> bool { match self { Film::RGB(f) => f.uses_visible_surface(), Film::GBuffer(f) => f.uses_visible_surface(), Film::Spectral(f) => f.uses_visible_surface(), } } pub fn full_resolution(&self) -> Point2i { self.base().full_resolution } pub fn pixel_bounds(&self) -> Bounds2i { self.base().pixel_bounds } pub fn sample_bounds(&self) -> Bounds2f { let pixel_bounds = self.pixel_bounds(); let radius = self.get_filter().radius(); Bounds2f::from_points( Point2f::from(pixel_bounds.p_min) - radius + Vector2f::new(0.5, 0.5), Point2f::from(pixel_bounds.p_max) + radius - Vector2f::new(0.5, 0.5), ) } pub fn diagonal(&self) -> Float { self.base().diagonal } pub fn get_filter(&self) -> &Filter { &self.base().filter } pub fn get_pixel_rgb(&self, _p: Point2i, _splat_scale: Option) -> RGB { todo!() } pub fn reset_pixel(&mut self, _p: Point2i) { todo!() } pub fn to_output_rgb(&self, _v: SampledSpectrum, _lambda: &SampledWavelengths) -> RGB { todo!() } }