742 lines
22 KiB
Rust
742 lines
22 KiB
Rust
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<Float, 3>,
|
|
pub pixels: Arc<Array2D<RGBPixel>>,
|
|
}
|
|
|
|
#[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<Float>) -> 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<GBufferPixel>,
|
|
colorspace: RGBColorSpace,
|
|
max_component_value: Float,
|
|
write_fp16: bool,
|
|
filter_integral: Float,
|
|
output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
|
}
|
|
|
|
#[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<Float>) -> 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<SpectralPixel>,
|
|
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
|
pub bucket_sums: Vec<f64>,
|
|
pub weight_sums: Vec<f64>,
|
|
pub bucket_splats: Vec<AtomicFloat>,
|
|
}
|
|
|
|
#[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<AtomicFloat> = (0..total_bucket_count)
|
|
.map(|_| AtomicFloat::new(0.0))
|
|
.collect();
|
|
|
|
let mut pixels = Array2D::<SpectralPixel>::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<Float, 3>,
|
|
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<Arc<Spectrum>>,
|
|
imaging_ratio: Float,
|
|
swatches: &[Spectrum; 24],
|
|
) -> Result<Self, Box<dyn Error>> {
|
|
// 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::<RGB>(
|
|
&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::<XYZ>(
|
|
&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<Arc<Spectrum>>,
|
|
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<Float, 3>;
|
|
|
|
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::<Float, 3>::default();
|
|
}
|
|
|
|
Self {
|
|
xyz_from_sensor_rgb,
|
|
r_bar,
|
|
g_bar,
|
|
b_bar,
|
|
imaging_ratio,
|
|
}
|
|
}
|
|
|
|
pub fn project_reflectance<T: Triplet>(
|
|
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<Float>) -> RGB {
|
|
todo!()
|
|
}
|
|
|
|
pub fn reset_pixel(&mut self, _p: Point2i) {
|
|
todo!()
|
|
}
|
|
|
|
pub fn to_output_rgb(&self, _v: SampledSpectrum, _lambda: &SampledWavelengths) -> RGB {
|
|
todo!()
|
|
}
|
|
}
|