Implementing Color as a simple enum rather than as a trait.

There's really no reason for it to be a Trait, and this will simplify
a lot of things down the road.
This commit is contained in:
Nathan Vegdahl 2018-12-27 16:09:03 -08:00
parent b598ce499c
commit 5add4cfdb1
10 changed files with 164 additions and 66 deletions

View File

@ -16,10 +16,6 @@ pub fn map_0_1_to_wavelength(n: f32) -> f32 {
n * WL_RANGE + WL_MIN n * WL_RANGE + WL_MIN
} }
pub trait Color {
fn to_spectral_sample(&self, hero_wavelength: f32) -> SpectralSample;
}
#[inline(always)] #[inline(always)]
fn nth_wavelength(hero_wavelength: f32, n: usize) -> f32 { fn nth_wavelength(hero_wavelength: f32, n: usize) -> f32 {
let wl = hero_wavelength + (WL_RANGE_Q * n as f32); let wl = hero_wavelength + (WL_RANGE_Q * n as f32);
@ -43,6 +39,126 @@ fn wavelengths(hero_wavelength: f32) -> Float4 {
//---------------------------------------------------------------- //----------------------------------------------------------------
#[derive(Debug, Copy, Clone)]
pub enum Color {
XYZ(f32, f32, f32),
Blackbody {
temperature: f32, // In kelvin
factor: f32, // Brightness multiplier
},
}
impl Color {
#[inline(always)]
pub fn new_xyz(xyz: (f32, f32, f32)) -> Self {
Color::XYZ(xyz.0, xyz.1, xyz.2)
}
#[inline(always)]
pub fn new_blackbody(temp: f32, fac: f32) -> Self {
Color::Blackbody {
temperature: temp,
factor: fac,
}
}
pub fn to_spectral_sample(self, hero_wavelength: f32) -> SpectralSample {
let wls = wavelengths(hero_wavelength);
match self {
Color::XYZ(x, y, z) => SpectralSample {
e: xyz_to_spectrum_4((x, y, z), wls),
hero_wavelength: hero_wavelength,
},
Color::Blackbody {
temperature,
factor,
} => {
SpectralSample::from_parts(
// TODO: make this SIMD
Float4::new(
plancks_law(temperature, wls.get_0()) * factor,
plancks_law(temperature, wls.get_1()) * factor,
plancks_law(temperature, wls.get_2()) * factor,
plancks_law(temperature, wls.get_3()) * factor,
),
hero_wavelength,
)
}
}
}
/// Calculates an approximate total spectral energy of the color.
///
/// Note: this really is very _approximate_.
pub fn approximate_energy(self) -> f32 {
// TODO: better approximation for Blackbody.
match self {
Color::XYZ(_, y, _) => y,
Color::Blackbody { factor, .. } => factor,
}
}
}
impl Lerp for Color {
/// Note that this isn't a proper lerp in spectral space. However,
/// for our purposes that should be fine: all we care about is that
/// the interpolation is smooth and "reasonable".
///
/// If at some point it turns out this causes artifacts, then we
/// also have bigger problems: texture filtering in the shading
/// pipeline will have the same issues, which will be even harder
/// to address. However, I strongly suspect this will not be an issue.
/// (Famous last words!)
fn lerp(self, other: Self, alpha: f32) -> Self {
let inv_alpha = 1.0 - alpha;
match (self, other) {
(Color::XYZ(x1, y1, z1), Color::XYZ(x2, y2, z2)) => Color::XYZ(
(x1 * inv_alpha) + (x2 * alpha),
(y1 * inv_alpha) + (y2 * alpha),
(z1 * inv_alpha) + (z2 * alpha),
),
(
Color::Blackbody {
temperature: tmp1,
factor: fac1,
},
Color::Blackbody {
temperature: tmp2,
factor: fac2,
},
) => Color::Blackbody {
temperature: (tmp1 * inv_alpha) + (tmp2 * alpha),
factor: (fac1 * inv_alpha) + (fac2 * alpha),
},
_ => panic!("Cannot lerp colors with different representations."),
}
}
}
fn plancks_law(temperature: f32, wavelength: f32) -> f32 {
const C: f32 = 299_792_458.0; // Speed of light
const H: f32 = 6.62607015e-34; // Planck constant
const KB: f32 = 1.38064852e-23; // Boltzmann constant
// // As written at https://en.wikipedia.org/wiki/Planck's_law, here for
// // reference and clarity:
// let a = (2.0 * H * C * C) / (wavelength * wavelength * wavelength * wavelength * wavelength);
// let b = 1.0 / (((H * C) / (wavelength * KB * temperature)).exp() - 1.0);
// a * b
// Optimized version of the commented code above:
const TMP1: f32 = (2.0f64 * H as f64 * C as f64 * C as f64) as f32;
const TMP2: f32 = (H as f64 * C as f64 / KB as f64) as f32;
let wl5 = {
let wl2 = wavelength * wavelength;
wl2 * wl2 * wavelength
};
let tmp3 = wl5 * (fast_exp(TMP2 / (wavelength * temperature)) - 1.0);
TMP1 / tmp3
}
//----------------------------------------------------------------
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct SpectralSample { pub struct SpectralSample {
pub e: Float4, pub e: Float4,
@ -197,15 +313,6 @@ impl XYZ {
} }
} }
impl Color for XYZ {
fn to_spectral_sample(&self, hero_wavelength: f32) -> SpectralSample {
SpectralSample {
e: xyz_to_spectrum_4((self.x, self.y, self.z), wavelengths(hero_wavelength)),
hero_wavelength: hero_wavelength,
}
}
}
impl Lerp for XYZ { impl Lerp for XYZ {
fn lerp(self, other: XYZ, alpha: f32) -> XYZ { fn lerp(self, other: XYZ, alpha: f32) -> XYZ {
(self * (1.0 - alpha)) + (other * alpha) (self * (1.0 - alpha)) + (other * alpha)

View File

@ -3,7 +3,7 @@ use std::f64::consts::PI as PI_64;
use mem_arena::MemArena; use mem_arena::MemArena;
use crate::{ use crate::{
color::{Color, SpectralSample, XYZ}, color::{Color, SpectralSample},
lerp::lerp_slice, lerp::lerp_slice,
math::{coordinate_system_from_vector, Vector}, math::{coordinate_system_from_vector, Vector},
sampling::{uniform_sample_cone, uniform_sample_cone_pdf}, sampling::{uniform_sample_cone, uniform_sample_cone_pdf},
@ -17,7 +17,7 @@ use super::WorldLightSource;
pub struct DistantDiskLight<'a> { pub struct DistantDiskLight<'a> {
radii: &'a [f32], radii: &'a [f32],
directions: &'a [Vector], directions: &'a [Vector],
colors: &'a [XYZ], colors: &'a [Color],
} }
impl<'a> DistantDiskLight<'a> { impl<'a> DistantDiskLight<'a> {
@ -25,7 +25,7 @@ impl<'a> DistantDiskLight<'a> {
arena: &'a MemArena, arena: &'a MemArena,
radii: &[f32], radii: &[f32],
directions: &[Vector], directions: &[Vector],
colors: &[XYZ], colors: &[Color],
) -> DistantDiskLight<'a> { ) -> DistantDiskLight<'a> {
DistantDiskLight { DistantDiskLight {
radii: arena.copy_slice(&radii), radii: arena.copy_slice(&radii),
@ -78,7 +78,7 @@ impl<'a> WorldLightSource for DistantDiskLight<'a> {
let sample = uniform_sample_cone(u, v, cos_theta_max).normalized(); let sample = uniform_sample_cone(u, v, cos_theta_max).normalized();
// Calculate the final values and return everything. // Calculate the final values and return everything.
let spectral_sample = (col * solid_angle_inv as f32).to_spectral_sample(wavelength); let spectral_sample = col.to_spectral_sample(wavelength) * solid_angle_inv as f32;
let shadow_vec = (x * sample.x()) + (y * sample.y()) + (z * sample.z()); let shadow_vec = (x * sample.x()) + (y * sample.y()) + (z * sample.z());
let pdf = uniform_sample_cone_pdf(cos_theta_max); let pdf = uniform_sample_cone_pdf(cos_theta_max);
(spectral_sample, shadow_vec, pdf as f32) (spectral_sample, shadow_vec, pdf as f32)
@ -89,11 +89,9 @@ impl<'a> WorldLightSource for DistantDiskLight<'a> {
} }
fn approximate_energy(&self) -> f32 { fn approximate_energy(&self) -> f32 {
let color: XYZ = self self.colors
.colors
.iter() .iter()
.fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b) .fold(0.0, |a, &b| a + b.approximate_energy())
/ self.colors.len() as f32; / self.colors.len() as f32
color.y
} }
} }

View File

@ -3,7 +3,7 @@ use mem_arena::MemArena;
use crate::{ use crate::{
bbox::BBox, bbox::BBox,
boundable::Boundable, boundable::Boundable,
color::{Color, SpectralSample, XYZ}, color::{Color, SpectralSample},
lerp::lerp_slice, lerp::lerp_slice,
math::{cross, dot, Matrix4x4, Normal, Point, Vector}, math::{cross, dot, Matrix4x4, Normal, Point, Vector},
ray::{AccelRay, Ray}, ray::{AccelRay, Ray},
@ -23,7 +23,7 @@ const SIMPLE_SAMPLING_THRESHOLD: f32 = 0.01;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct RectangleLight<'a> { pub struct RectangleLight<'a> {
dimensions: &'a [(f32, f32)], dimensions: &'a [(f32, f32)],
colors: &'a [XYZ], colors: &'a [Color],
bounds_: &'a [BBox], bounds_: &'a [BBox],
} }
@ -31,7 +31,7 @@ impl<'a> RectangleLight<'a> {
pub fn new<'b>( pub fn new<'b>(
arena: &'b MemArena, arena: &'b MemArena,
dimensions: &[(f32, f32)], dimensions: &[(f32, f32)],
colors: &[XYZ], colors: &[Color],
) -> RectangleLight<'b> { ) -> RectangleLight<'b> {
let bbs: Vec<_> = dimensions let bbs: Vec<_> = dimensions
.iter() .iter()
@ -188,7 +188,7 @@ impl<'a> SurfaceLight for RectangleLight<'a> {
.into_point(); .into_point();
let shadow_vec = sample_point - arr; let shadow_vec = sample_point - arr;
let spectral_sample = let spectral_sample =
(col * surface_area_inv as f32 * 0.5).to_spectral_sample(wavelength); (col).to_spectral_sample(wavelength) * surface_area_inv as f32 * 0.5;
let pdf = (sample_point - arr).length2() let pdf = (sample_point - arr).length2()
/ dot(shadow_vec.normalized(), normal.into_vector().normalized()).abs() / dot(shadow_vec.normalized(), normal.into_vector().normalized()).abs()
/ (surface_area_1 + surface_area_2); / (surface_area_1 + surface_area_2);
@ -232,7 +232,7 @@ impl<'a> SurfaceLight for RectangleLight<'a> {
// Calculate pdf and light energy // Calculate pdf and light energy
let pdf = 1.0 / (area_1 + area_2); // PDF of the ray direction being sampled let pdf = 1.0 / (area_1 + area_2); // PDF of the ray direction being sampled
let spectral_sample = let spectral_sample =
(col * surface_area_inv as f32 * 0.5).to_spectral_sample(wavelength); col.to_spectral_sample(wavelength) * surface_area_inv as f32 * 0.5;
( (
spectral_sample, spectral_sample,
@ -247,12 +247,10 @@ impl<'a> SurfaceLight for RectangleLight<'a> {
} }
fn approximate_energy(&self) -> f32 { fn approximate_energy(&self) -> f32 {
let color: XYZ = self self.colors
.colors
.iter() .iter()
.fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b) .fold(0.0, |a, &b| a + b.approximate_energy())
/ self.colors.len() as f32; / self.colors.len() as f32
color.y
} }
} }

View File

@ -5,7 +5,7 @@ use mem_arena::MemArena;
use crate::{ use crate::{
bbox::BBox, bbox::BBox,
boundable::Boundable, boundable::Boundable,
color::{Color, SpectralSample, XYZ}, color::{Color, SpectralSample},
lerp::lerp_slice, lerp::lerp_slice,
math::{coordinate_system_from_vector, dot, Matrix4x4, Normal, Point, Vector}, math::{coordinate_system_from_vector, dot, Matrix4x4, Normal, Point, Vector},
ray::{AccelRay, Ray}, ray::{AccelRay, Ray},
@ -26,12 +26,12 @@ const SAMPLE_POINT_FUDGE: f32 = 0.001;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct SphereLight<'a> { pub struct SphereLight<'a> {
radii: &'a [f32], radii: &'a [f32],
colors: &'a [XYZ], colors: &'a [Color],
bounds_: &'a [BBox], bounds_: &'a [BBox],
} }
impl<'a> SphereLight<'a> { impl<'a> SphereLight<'a> {
pub fn new<'b>(arena: &'b MemArena, radii: &[f32], colors: &[XYZ]) -> SphereLight<'b> { pub fn new<'b>(arena: &'b MemArena, radii: &[f32], colors: &[Color]) -> SphereLight<'b> {
let bbs: Vec<_> = radii let bbs: Vec<_> = radii
.iter() .iter()
.map(|r| BBox { .map(|r| BBox {
@ -164,7 +164,7 @@ impl<'a> SurfaceLight for SphereLight<'a> {
) )
}; };
let pdf = uniform_sample_cone_pdf(cos_theta_max); let pdf = uniform_sample_cone_pdf(cos_theta_max);
let spectral_sample = (col * surface_area_inv as f32).to_spectral_sample(wavelength); let spectral_sample = col.to_spectral_sample(wavelength) * surface_area_inv as f32;
return ( return (
spectral_sample, spectral_sample,
(sample_point, normal, sample_point_err), (sample_point, normal, sample_point_err),
@ -182,7 +182,7 @@ impl<'a> SurfaceLight for SphereLight<'a> {
) )
}; };
let pdf = 1.0 / (4.0 * PI_64); let pdf = 1.0 / (4.0 * PI_64);
let spectral_sample = (col * surface_area_inv as f32).to_spectral_sample(wavelength); let spectral_sample = col.to_spectral_sample(wavelength) * surface_area_inv as f32;
return ( return (
spectral_sample, spectral_sample,
(sample_point, normal, sample_point_err), (sample_point, normal, sample_point_err),
@ -196,12 +196,10 @@ impl<'a> SurfaceLight for SphereLight<'a> {
} }
fn approximate_energy(&self) -> f32 { fn approximate_energy(&self) -> f32 {
let color: XYZ = self self.colors
.colors
.iter() .iter()
.fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b) .fold(0.0, |a, &b| a + b.approximate_energy())
/ self.colors.len() as f32; / self.colors.len() as f32
color.y
} }
} }

View File

@ -8,7 +8,7 @@ use mem_arena::MemArena;
use crate::{ use crate::{
camera::Camera, camera::Camera,
color::{rec709_e_to_xyz, XYZ}, color::{rec709_e_to_xyz, Color},
light::WorldLightSource, light::WorldLightSource,
math::Matrix4x4, math::Matrix4x4,
renderer::Renderer, renderer::Renderer,
@ -504,7 +504,7 @@ fn parse_world<'a>(arena: &'a MemArena, tree: &'a DataTree) -> Result<World<'a>,
{ {
// TODO: proper color space management, not just assuming // TODO: proper color space management, not just assuming
// rec.709. // rec.709.
background_color = XYZ::from_tuple(rec709_e_to_xyz(color)); background_color = Color::new_xyz(rec709_e_to_xyz(color));
} else { } else {
return Err(PsyParseError::IncorrectLeafData( return Err(PsyParseError::IncorrectLeafData(
byte_offset, byte_offset,

View File

@ -7,7 +7,7 @@ use nom::{call, closure, tuple, tuple_parser, IResult};
use mem_arena::MemArena; use mem_arena::MemArena;
use crate::{ use crate::{
color::{rec709_e_to_xyz, XYZ}, color::{rec709_e_to_xyz, Color},
light::{DistantDiskLight, RectangleLight, SphereLight}, light::{DistantDiskLight, RectangleLight, SphereLight},
math::Vector, math::Vector,
}; };
@ -68,7 +68,7 @@ pub fn parse_distant_disk_light<'a>(
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
colors.push(XYZ::from_tuple(rec709_e_to_xyz(color))); colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -122,7 +122,7 @@ pub fn parse_sphere_light<'a>(
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
colors.push(XYZ::from_tuple(rec709_e_to_xyz(color))); colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -178,7 +178,7 @@ pub fn parse_rectangle_light<'a>(
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
colors.push(XYZ::from_tuple(rec709_e_to_xyz(color))); colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));

View File

@ -7,7 +7,7 @@ use nom::{call, closure, tuple, tuple_parser, IResult};
use mem_arena::MemArena; use mem_arena::MemArena;
use crate::{ use crate::{
color::{rec709_e_to_xyz, XYZ}, color::{rec709_e_to_xyz, Color},
shading::{SimpleSurfaceShader, SurfaceShader}, shading::{SimpleSurfaceShader, SurfaceShader},
}; };
@ -44,7 +44,7 @@ pub fn parse_surface_shader<'a>(
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
XYZ::from_tuple(rec709_e_to_xyz(color)) Color::new_xyz(rec709_e_to_xyz(color))
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -68,7 +68,7 @@ pub fn parse_surface_shader<'a>(
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
XYZ::from_tuple(rec709_e_to_xyz(color)) Color::new_xyz(rec709_e_to_xyz(color))
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -93,7 +93,7 @@ pub fn parse_surface_shader<'a>(
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
XYZ::from_tuple(rec709_e_to_xyz(color)) Color::new_xyz(rec709_e_to_xyz(color))
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -172,7 +172,7 @@ pub fn parse_surface_shader<'a>(
// TODO: handle color space conversions properly. // TODO: handle color space conversions properly.
// Probably will need a special color type with its // Probably will need a special color type with its
// own parser...? // own parser...?
XYZ::from_tuple(rec709_e_to_xyz(color)) Color::new_xyz(rec709_e_to_xyz(color))
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));

View File

@ -1,7 +1,7 @@
use crate::{color::XYZ, light::WorldLightSource}; use crate::{color::Color, light::WorldLightSource};
#[derive(Debug)] #[derive(Debug)]
pub struct World<'a> { pub struct World<'a> {
pub background_color: XYZ, pub background_color: Color,
pub lights: &'a [&'a WorldLightSource], pub lights: &'a [&'a WorldLightSource],
} }

View File

@ -2,10 +2,7 @@ pub mod surface_closure;
use std::fmt::Debug; use std::fmt::Debug;
use crate::{ use crate::{color::Color, surface::SurfaceIntersectionData};
color::{Color, XYZ},
surface::SurfaceIntersectionData,
};
use self::surface_closure::{ use self::surface_closure::{
EmitClosure, GGXClosure, GTRClosure, LambertClosure, SurfaceClosureUnion, EmitClosure, GGXClosure, GTRClosure, LambertClosure, SurfaceClosureUnion,
@ -37,19 +34,19 @@ pub trait SurfaceShader: Debug + Sync {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum SimpleSurfaceShader { pub enum SimpleSurfaceShader {
Emit { Emit {
color: XYZ, color: Color,
}, },
Lambert { Lambert {
color: XYZ, color: Color,
}, },
GTR { GTR {
color: XYZ, color: Color,
roughness: f32, roughness: f32,
tail_shape: f32, tail_shape: f32,
fresnel: f32, fresnel: f32,
}, },
GGX { GGX {
color: XYZ, color: Color,
roughness: f32, roughness: f32,
fresnel: f32, fresnel: f32,
}, },

View File

@ -2,7 +2,7 @@ use std::iter;
use crate::{ use crate::{
algorithm::partition, algorithm::partition,
color::{rec709_to_xyz, XYZ}, color::{rec709_to_xyz, Color},
lerp::lerp_slice, lerp::lerp_slice,
ray::{AccelRay, Ray}, ray::{AccelRay, Ray},
scene::{Assembly, InstanceType, Object}, scene::{Assembly, InstanceType, Object},
@ -182,7 +182,7 @@ impl<'a> TracerInner<'a> {
match *obj { match *obj {
Object::Surface(surface) => { Object::Surface(surface) => {
let unassigned_shader = SimpleSurfaceShader::Emit { let unassigned_shader = SimpleSurfaceShader::Emit {
color: XYZ::from_tuple(rec709_to_xyz((1.0, 0.0, 1.0))), color: Color::new_xyz(rec709_to_xyz((1.0, 0.0, 1.0))),
}; };
let shader = surface_shader.unwrap_or(&unassigned_shader); let shader = surface_shader.unwrap_or(&unassigned_shader);
@ -198,7 +198,7 @@ impl<'a> TracerInner<'a> {
Object::SurfaceLight(surface) => { Object::SurfaceLight(surface) => {
// Lights don't use shaders // Lights don't use shaders
let bogus_shader = SimpleSurfaceShader::Emit { let bogus_shader = SimpleSurfaceShader::Emit {
color: XYZ::from_tuple(rec709_to_xyz((1.0, 0.0, 1.0))), color: Color::new_xyz(rec709_to_xyz((1.0, 0.0, 1.0))),
}; };
surface.intersect_rays( surface.intersect_rays(