618 lines
17 KiB
Rust
618 lines
17 KiB
Rust
use std::fmt;
|
|
use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Neg, Index, IndexMut};
|
|
use once_cell::sync::Lazy;
|
|
use std::sync::Arc;
|
|
|
|
use crate::core::pbrt::Float;
|
|
use crate::core::cie;
|
|
use super::color::{RGB, XYZ, RGBSigmoidPolynomial};
|
|
use super::colorspace::RGBColorspace;
|
|
|
|
pub const CIE_Y_INTEGRAL: Float = 106.856895;
|
|
pub const N_SPECTRUM_SAMPLES: usize = 1200;
|
|
pub const LAMBDA_MIN: i32 = 360;
|
|
pub const LAMBDA_MAX: i32 = 830;
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct SampledSpectrum {
|
|
values: [Float; N_SPECTRUM_SAMPLES],
|
|
}
|
|
|
|
impl Default for SampledSpectrum {
|
|
fn default() -> Self { Self { values: [0.0; N_SPECTRUM_SAMPLES] } }
|
|
}
|
|
|
|
impl SampledSpectrum {
|
|
pub fn new(c: Float) -> Self {
|
|
Self {
|
|
values: [c; N_SPECTRUM_SAMPLES],
|
|
}
|
|
}
|
|
|
|
pub fn from_vector(v: Vec<Float>) -> Self {
|
|
let mut s = Self::new(0.0);
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
s.values[i] = v[i];
|
|
}
|
|
s
|
|
}
|
|
|
|
pub fn has_nans(&self) -> bool {
|
|
self.values.iter().any(|&v| v.is_nan())
|
|
}
|
|
|
|
pub fn min_component_value(&self) -> Float {
|
|
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b))
|
|
}
|
|
|
|
pub fn max_component_value(&self) -> Float {
|
|
self.values.iter().fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
|
}
|
|
|
|
pub fn average(&self) -> Float {
|
|
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
|
|
}
|
|
|
|
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
|
let mut r = SampledSpectrum::new(0.0);
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
r.values[i] = if rhs[i] != 0.0 {self.values[i] / rhs.values[i]} else { 0.0 }
|
|
}
|
|
r
|
|
}
|
|
|
|
pub fn to_xyz(&self, lambda: &SampledWavelengths) -> XYZ {
|
|
let x = spectra::X.sample(lambda);
|
|
let y = spectra::Y.sample(lambda);
|
|
let z = spectra::Z.sample(lambda);
|
|
let pdf = lambda.pdf();
|
|
XYZ::new(
|
|
(*self * x).safe_div(pdf).average(),
|
|
(*self * y).safe_div(pdf).average(),
|
|
(*self * z).safe_div(pdf).average(),
|
|
) / CIE_Y_INTEGRAL
|
|
}
|
|
|
|
pub fn to_rgb(&self, lambda: &SampledWavelengths, c: &RGBColorspace) -> RGB {
|
|
let xyz = self.to_xyz(lambda);
|
|
c.to_rgb(xyz)
|
|
}
|
|
}
|
|
|
|
|
|
impl Index<usize> for SampledSpectrum {
|
|
type Output = Float;
|
|
fn index(&self, i: usize) -> &Self::Output { &self.values[i] }
|
|
}
|
|
|
|
impl IndexMut<usize> for SampledSpectrum {
|
|
fn index_mut(&mut self, i: usize) -> &mut Self::Output { &mut self.values[i] }
|
|
}
|
|
|
|
impl Add for SampledSpectrum {
|
|
type Output = Self;
|
|
fn add(self, rhs: Self) -> Self {
|
|
let mut ret = self;
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
ret.values[i] += rhs.values[i];
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl AddAssign for SampledSpectrum {
|
|
fn add_assign(&mut self, rhs: Self) {
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
self.values[i] += rhs.values[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Sub for SampledSpectrum {
|
|
type Output = Self;
|
|
fn sub(self, rhs: Self) -> Self {
|
|
let mut ret = self;
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
ret.values[i] -= rhs.values[i];
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl SubAssign for SampledSpectrum {
|
|
fn sub_assign(&mut self, rhs: Self) {
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
self.values[i] -= rhs.values[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Sub<SampledSpectrum> for Float {
|
|
type Output = SampledSpectrum;
|
|
fn sub(self, rhs: SampledSpectrum) -> SampledSpectrum {
|
|
let mut ret = SampledSpectrum::new(0.0);
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
ret.values[i] = self - rhs.values[i];
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl Mul for SampledSpectrum {
|
|
type Output = Self;
|
|
fn mul(self, rhs: Self) -> Self {
|
|
let mut ret = self;
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
ret.values[i] *= rhs.values[i];
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl MulAssign for SampledSpectrum {
|
|
fn mul_assign(&mut self, rhs: Self) {
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
self.values[i] *= rhs.values[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Mul<Float> for SampledSpectrum {
|
|
type Output = Self;
|
|
fn mul(self, rhs: Float) -> Self {
|
|
let mut ret = self;
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
ret.values[i] *= rhs;
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl Mul<SampledSpectrum> for Float {
|
|
type Output = SampledSpectrum;
|
|
fn mul(self, rhs: SampledSpectrum) -> SampledSpectrum {
|
|
rhs * self
|
|
}
|
|
}
|
|
|
|
impl MulAssign<Float> for SampledSpectrum {
|
|
fn mul_assign(&mut self, rhs: Float) {
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
self.values[i] *= rhs;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DivAssign for SampledSpectrum {
|
|
fn div_assign(&mut self, rhs: Self) {
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
debug_assert_ne!(0.0, rhs.values[i]);
|
|
self.values[i] /= rhs.values[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Div for SampledSpectrum {
|
|
type Output = Self;
|
|
fn div(self, rhs: Self) -> Self::Output {
|
|
let mut ret = self;
|
|
ret /= rhs;
|
|
ret
|
|
}
|
|
}
|
|
impl Div<Float> for SampledSpectrum {
|
|
type Output = Self;
|
|
|
|
fn div(self, rhs: Float) -> Self::Output {
|
|
debug_assert_ne!(rhs, 0.0);
|
|
let mut ret = self;
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
ret.values[i] /= rhs;
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl DivAssign<Float> for SampledSpectrum {
|
|
fn div_assign(&mut self, rhs: Float) {
|
|
debug_assert_ne!(rhs, 0.0);
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
self.values[i] /= rhs;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Neg for SampledSpectrum {
|
|
type Output = Self;
|
|
|
|
fn neg(self) -> Self::Output {
|
|
let mut ret = SampledSpectrum::new(0.0);
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
ret.values[i] = -self.values[i];
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub struct SampledWavelengths {
|
|
pub lambda: [Float; N_SPECTRUM_SAMPLES],
|
|
pub pdf: [Float; N_SPECTRUM_SAMPLES],
|
|
}
|
|
|
|
impl SampledWavelengths {
|
|
pub fn pdf(&self) -> SampledSpectrum {
|
|
SampledSpectrum::from_vector(self.pdf.to_vec())
|
|
}
|
|
|
|
pub fn secondary_terminated(&self) -> bool {
|
|
for i in 1..N_SPECTRUM_SAMPLES {
|
|
if self.pdf[i] != 0.0 {
|
|
return false
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub fn terminate_secondary(&mut self) {
|
|
if !self.secondary_terminated() {
|
|
for i in 1..N_SPECTRUM_SAMPLES {
|
|
self.pdf[i] = 0.0;
|
|
}
|
|
self.pdf[0] /= N_SPECTRUM_SAMPLES as Float;
|
|
}
|
|
}
|
|
|
|
pub fn sample_uniform(u: Float, lambda_min: Float, lambda_max: Float) -> Self {
|
|
let mut lambda = [0.0; N_SPECTRUM_SAMPLES];
|
|
lambda[0] = crate::core::pbrt::lerp(u, lambda_min, lambda_min);
|
|
let delta = (lambda_max - lambda_min) / N_SPECTRUM_SAMPLES as Float;
|
|
for i in 1..N_SPECTRUM_SAMPLES {
|
|
lambda[i] = lambda[i - 1] + delta;
|
|
if lambda[i] > lambda_max {
|
|
lambda[i] = lambda_min + (lambda[i] - lambda_max);
|
|
}
|
|
}
|
|
|
|
let mut pdf = [0.0; N_SPECTRUM_SAMPLES];
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
pdf[i] = 1.0 / (lambda_max - lambda_min);
|
|
}
|
|
|
|
Self { lambda, pdf }
|
|
}
|
|
|
|
pub fn sample_visible_wavelengths(u: Float) -> Float {
|
|
538.0 - 138.888889 * Float::atanh(0.85691062 - 1.82750197 * u)
|
|
}
|
|
|
|
pub fn visible_wavelengths_pdf(lambda: Float) -> Float {
|
|
if lambda < 360.0 || lambda > 830.0 { return 0.0; }
|
|
0.0039398042 / (Float::cosh(0.0072 * (lambda - 538.0))).sqrt()
|
|
}
|
|
|
|
pub fn sample_visible(u: Float) -> Self {
|
|
let mut lambda = [0.0; N_SPECTRUM_SAMPLES];
|
|
let mut pdf = [0.0; N_SPECTRUM_SAMPLES];
|
|
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
let mut up = u + i as Float / N_SPECTRUM_SAMPLES as Float;
|
|
if up > 1.0 { up -= 1.0; }
|
|
lambda[i] = Self::sample_visible_wavelengths(up);
|
|
pdf[i] = Self::visible_wavelengths_pdf(lambda[i]);
|
|
}
|
|
Self { lambda, pdf }
|
|
}
|
|
}
|
|
|
|
impl Index<usize> for SampledWavelengths {
|
|
type Output = Float;
|
|
fn index(&self, i: usize) -> &Self::Output { &self.lambda[i] }
|
|
}
|
|
|
|
impl IndexMut<usize> for SampledWavelengths {
|
|
fn index_mut(&mut self, i: usize) -> &mut Self::Output { &mut self.lambda[i] }
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Spectrum {
|
|
Constant(ConstantSpectrum),
|
|
DenselySampled(DenselySampledSpectrum),
|
|
PiecewiseLinear(PiecewiseLinearSpectrum),
|
|
Blackbody(BlackbodySpectrum),
|
|
RGBAlbedo(RGBAlbedoSpectrum),
|
|
}
|
|
|
|
impl Spectrum {
|
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
|
match self {
|
|
Spectrum::Constant(s) => s.sample_at(lambda),
|
|
Spectrum::DenselySampled(s) => s.sample_at(lambda),
|
|
Spectrum::PiecewiseLinear(s) => s.sample_at(lambda),
|
|
Spectrum::Blackbody(s) => s.sample_at(lambda),
|
|
Spectrum::RGBAlbedo(s) => s.sample_at(lambda),
|
|
}
|
|
}
|
|
|
|
pub fn max_value(&self) -> Float {
|
|
match self {
|
|
Spectrum::Constant(_s) => 0.0,
|
|
Spectrum::DenselySampled(_s) => 0.0,
|
|
Spectrum::PiecewiseLinear(_s) => 0.0,
|
|
Spectrum::Blackbody(_s) => 0.0,
|
|
Spectrum::RGBAlbedo(s) => s.max_value(),
|
|
}
|
|
}
|
|
|
|
pub fn sample(&self, wavelengths: &SampledWavelengths) -> SampledSpectrum {
|
|
let mut s = SampledSpectrum::default();
|
|
for i in 0..N_SPECTRUM_SAMPLES {
|
|
s[i] = self.sample_at(wavelengths[i]);
|
|
}
|
|
s
|
|
}
|
|
|
|
pub fn to_xyz(&self) -> XYZ {
|
|
XYZ::new(inner_product(&spectra::X, self),
|
|
inner_product(&spectra::Y, self),
|
|
inner_product(&spectra::Z, self))/CIE_Y_INTEGRAL
|
|
}
|
|
}
|
|
|
|
pub fn inner_product(f: &Spectrum, g: &Spectrum) -> Float {
|
|
let mut integral = 0.0;
|
|
for lambda in LAMBDA_MIN..=LAMBDA_MAX {
|
|
integral += f.sample_at(lambda as f32) * g.sample_at(lambda as f32);
|
|
}
|
|
integral
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct ConstantSpectrum {
|
|
c: Float,
|
|
}
|
|
|
|
impl ConstantSpectrum {
|
|
pub fn new(c: Float) -> Self { Self { c } }
|
|
|
|
pub fn sample_at(&self, _lambda: Float) -> Float {
|
|
self.c
|
|
}
|
|
|
|
fn max_value(&self) -> Float { self.c }
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct RGBAlbedoSpectrum {
|
|
rsp: RGBSigmoidPolynomial,
|
|
}
|
|
|
|
impl RGBAlbedoSpectrum {
|
|
pub fn new(cs: &RGBColorspace, rgb: RGB) -> Self {
|
|
Self { rsp: cs.to_rgb_coeffs(rgb) }
|
|
}
|
|
|
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
|
self.rsp.evaluate(lambda)
|
|
}
|
|
|
|
pub fn max_value(&self) -> Float {
|
|
self.rsp.max_value()
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct UnboundedRGBSpectrum {
|
|
scale: Float,
|
|
rsp: RGBSigmoidPolynomial,
|
|
}
|
|
|
|
impl UnboundedRGBSpectrum {
|
|
pub fn new(cs: RGBColorspace, rgb: RGB) -> Self {
|
|
let m = rgb.r.max(rgb.g).max(rgb.b);
|
|
let scale = 2.0 * m;
|
|
let scaled_rgb = if scale != 0.0 { rgb / scale } else { RGB::new(0.0, 0.0, 0.0) };
|
|
Self { scale, rsp: cs.to_rgb_coeffs(scaled_rgb) }
|
|
}
|
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
|
self.scale * self.rsp.evaluate(lambda)
|
|
}
|
|
|
|
pub fn max_value(&self) -> Float {
|
|
self.scale * self.rsp.max_value()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct RGBIlluminantSpectrum {
|
|
scale: Float,
|
|
rsp: RGBSigmoidPolynomial,
|
|
illuminant: Option<Arc<DenselySampledSpectrum>>,
|
|
}
|
|
|
|
impl RGBIlluminantSpectrum {
|
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
|
match &self.illuminant {
|
|
Some(illuminant) => self.scale * self.rsp.evaluate(lambda) * illuminant.sample_at(lambda),
|
|
None => 0.0,
|
|
}
|
|
}
|
|
|
|
pub fn max_value(&self) -> Float {
|
|
match &self.illuminant {
|
|
Some(illuminant) => self.scale * self.rsp.max_value() * illuminant.max_value(),
|
|
None => 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct DenselySampledSpectrum {
|
|
lambda_min: i32,
|
|
lambda_max: i32,
|
|
values: Vec<Float>,
|
|
}
|
|
|
|
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] }
|
|
}
|
|
|
|
pub fn from_spectrum(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.sample_at(lambda as Float);
|
|
}
|
|
s
|
|
}
|
|
|
|
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self where F: Fn(Float) -> Float {
|
|
let mut s = Self::new(lambda_min, lambda_max);
|
|
if s.values.is_empty() { return s; }
|
|
for lambda in lambda_min..=lambda_max {
|
|
let index = (lambda - lambda_min) as usize;
|
|
s.values[index] = f(lambda as Float);
|
|
}
|
|
s
|
|
}
|
|
|
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
|
let offset = (lambda.round() as i32) - self.lambda_min;
|
|
if offset < 0 || offset as usize >= self.values.len() {
|
|
0.0
|
|
} else {
|
|
self.values[offset as usize]
|
|
}
|
|
}
|
|
|
|
pub fn max_value(&self) -> Float {
|
|
self.values.iter().fold(Float::MIN, |a,b| a.max(*b))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PiecewiseLinearSpectrum {
|
|
samples: Vec<(Float, Float)>,
|
|
}
|
|
|
|
impl PiecewiseLinearSpectrum {
|
|
pub fn from_interleaved(data: &[Float]) -> Self {
|
|
assert!(data.len() % 2 == 0, "Interleaved data must have an even number of elements");
|
|
let mut samples = Vec::new();
|
|
for pair in data.chunks(2) {
|
|
samples.push((pair[0], pair[1]));
|
|
}
|
|
// PBRT requires the samples to be sorted by wavelength for interpolation.
|
|
samples.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
|
Self { samples }
|
|
}
|
|
|
|
fn sample_at(&self, lambda: Float) -> Float {
|
|
if self.samples.is_empty() {
|
|
return 0.0;
|
|
}
|
|
// Handle boundary conditions
|
|
if lambda <= self.samples[0].0 {
|
|
return self.samples[0].1;
|
|
}
|
|
if lambda >= self.samples.last().unwrap().0 {
|
|
return self.samples.last().unwrap().1;
|
|
}
|
|
|
|
let i = self.samples.partition_point(|s| s.0 < lambda);
|
|
let s1 = self.samples[i - 1];
|
|
let s2 = self.samples[i];
|
|
|
|
let t = (lambda - s1.0) / (s2.0 - s1.0);
|
|
(1.0 - t) * s1.1 + t * s2.1
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct BlackbodySpectrum {
|
|
temperature: Float,
|
|
normalization_factor: Float,
|
|
}
|
|
|
|
// Planck's Law
|
|
impl BlackbodySpectrum {
|
|
const C: Float = 299792458.0;
|
|
const H: Float = 6.62606957e-34;
|
|
const KB: Float = 1.3806488e-23;
|
|
|
|
pub fn new(temperature: Float) -> Self {
|
|
let lambda_max = 2.8977721e-3 / temperature * 1e9;
|
|
let max_val = Self::planck_law(lambda_max, temperature);
|
|
Self {
|
|
temperature,
|
|
normalization_factor: if max_val > 0.0 { 1.0 / max_val } else { 0.0 },
|
|
}
|
|
}
|
|
|
|
fn planck_law(lambda_nm: Float, temp: Float) -> Float {
|
|
if temp <= 0.0 { return 0.0; }
|
|
let lambda_m = lambda_nm * 1e-9; // Convert nm to meters
|
|
let c1 = 2.0 * Self::H * Self::C * Self::C;
|
|
let c2 = (Self::H * Self::C) / Self::KB;
|
|
|
|
let numerator = c1 / lambda_m.powi(5);
|
|
let denominator = (c2 / (lambda_m * temp)).exp() - 1.0;
|
|
|
|
if denominator.is_infinite() { 0.0 } else { numerator / denominator }
|
|
}
|
|
|
|
fn sample_at(&self, lambda: Float) -> Float {
|
|
Self::planck_law(lambda, self.temperature) * self.normalization_factor
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct RGBSpectrum { pub c: [Float; 3] }
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct RGBUnboundedSpectrum(pub RGBSpectrum);
|
|
|
|
pub mod spectra {
|
|
use super::*;
|
|
pub static X: Lazy<Spectrum> = Lazy::new(|| {
|
|
let pls = PiecewiseLinearSpectrum::from_interleaved(&cie::CIE_X);
|
|
let dss = DenselySampledSpectrum::from_spectrum(
|
|
&Spectrum::PiecewiseLinear(pls),
|
|
LAMBDA_MIN,
|
|
LAMBDA_MAX,
|
|
);
|
|
Spectrum::DenselySampled(dss)
|
|
});
|
|
pub static Y: Lazy<Spectrum> = Lazy::new(|| {
|
|
let pls = PiecewiseLinearSpectrum::from_interleaved(&cie::CIE_Y);
|
|
let dss = DenselySampledSpectrum::from_spectrum(
|
|
&Spectrum::PiecewiseLinear(pls),
|
|
LAMBDA_MIN,
|
|
LAMBDA_MAX,
|
|
);
|
|
Spectrum::DenselySampled(dss)
|
|
});
|
|
pub static Z: Lazy<Spectrum> = Lazy::new(|| {
|
|
let pls = PiecewiseLinearSpectrum::from_interleaved(&cie::CIE_Z);
|
|
let dss = DenselySampledSpectrum::from_spectrum(
|
|
&Spectrum::PiecewiseLinear(pls),
|
|
LAMBDA_MIN,
|
|
LAMBDA_MAX,
|
|
);
|
|
Spectrum::DenselySampled(dss)
|
|
});
|
|
|
|
}
|