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:
parent
b598ce499c
commit
5add4cfdb1
133
src/color.rs
133
src/color.rs
|
@ -16,10 +16,6 @@ pub fn map_0_1_to_wavelength(n: f32) -> f32 {
|
|||
n * WL_RANGE + WL_MIN
|
||||
}
|
||||
|
||||
pub trait Color {
|
||||
fn to_spectral_sample(&self, hero_wavelength: f32) -> SpectralSample;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn nth_wavelength(hero_wavelength: f32, n: usize) -> 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)]
|
||||
pub struct SpectralSample {
|
||||
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 {
|
||||
fn lerp(self, other: XYZ, alpha: f32) -> XYZ {
|
||||
(self * (1.0 - alpha)) + (other * alpha)
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::f64::consts::PI as PI_64;
|
|||
use mem_arena::MemArena;
|
||||
|
||||
use crate::{
|
||||
color::{Color, SpectralSample, XYZ},
|
||||
color::{Color, SpectralSample},
|
||||
lerp::lerp_slice,
|
||||
math::{coordinate_system_from_vector, Vector},
|
||||
sampling::{uniform_sample_cone, uniform_sample_cone_pdf},
|
||||
|
@ -17,7 +17,7 @@ use super::WorldLightSource;
|
|||
pub struct DistantDiskLight<'a> {
|
||||
radii: &'a [f32],
|
||||
directions: &'a [Vector],
|
||||
colors: &'a [XYZ],
|
||||
colors: &'a [Color],
|
||||
}
|
||||
|
||||
impl<'a> DistantDiskLight<'a> {
|
||||
|
@ -25,7 +25,7 @@ impl<'a> DistantDiskLight<'a> {
|
|||
arena: &'a MemArena,
|
||||
radii: &[f32],
|
||||
directions: &[Vector],
|
||||
colors: &[XYZ],
|
||||
colors: &[Color],
|
||||
) -> DistantDiskLight<'a> {
|
||||
DistantDiskLight {
|
||||
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();
|
||||
|
||||
// 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 pdf = uniform_sample_cone_pdf(cos_theta_max);
|
||||
(spectral_sample, shadow_vec, pdf as f32)
|
||||
|
@ -89,11 +89,9 @@ impl<'a> WorldLightSource for DistantDiskLight<'a> {
|
|||
}
|
||||
|
||||
fn approximate_energy(&self) -> f32 {
|
||||
let color: XYZ = self
|
||||
.colors
|
||||
self.colors
|
||||
.iter()
|
||||
.fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b)
|
||||
/ self.colors.len() as f32;
|
||||
color.y
|
||||
.fold(0.0, |a, &b| a + b.approximate_energy())
|
||||
/ self.colors.len() as f32
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use mem_arena::MemArena;
|
|||
use crate::{
|
||||
bbox::BBox,
|
||||
boundable::Boundable,
|
||||
color::{Color, SpectralSample, XYZ},
|
||||
color::{Color, SpectralSample},
|
||||
lerp::lerp_slice,
|
||||
math::{cross, dot, Matrix4x4, Normal, Point, Vector},
|
||||
ray::{AccelRay, Ray},
|
||||
|
@ -23,7 +23,7 @@ const SIMPLE_SAMPLING_THRESHOLD: f32 = 0.01;
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct RectangleLight<'a> {
|
||||
dimensions: &'a [(f32, f32)],
|
||||
colors: &'a [XYZ],
|
||||
colors: &'a [Color],
|
||||
bounds_: &'a [BBox],
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl<'a> RectangleLight<'a> {
|
|||
pub fn new<'b>(
|
||||
arena: &'b MemArena,
|
||||
dimensions: &[(f32, f32)],
|
||||
colors: &[XYZ],
|
||||
colors: &[Color],
|
||||
) -> RectangleLight<'b> {
|
||||
let bbs: Vec<_> = dimensions
|
||||
.iter()
|
||||
|
@ -188,7 +188,7 @@ impl<'a> SurfaceLight for RectangleLight<'a> {
|
|||
.into_point();
|
||||
let shadow_vec = sample_point - arr;
|
||||
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()
|
||||
/ dot(shadow_vec.normalized(), normal.into_vector().normalized()).abs()
|
||||
/ (surface_area_1 + surface_area_2);
|
||||
|
@ -232,7 +232,7 @@ impl<'a> SurfaceLight for RectangleLight<'a> {
|
|||
// Calculate pdf and light energy
|
||||
let pdf = 1.0 / (area_1 + area_2); // PDF of the ray direction being sampled
|
||||
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,
|
||||
|
@ -247,12 +247,10 @@ impl<'a> SurfaceLight for RectangleLight<'a> {
|
|||
}
|
||||
|
||||
fn approximate_energy(&self) -> f32 {
|
||||
let color: XYZ = self
|
||||
.colors
|
||||
self.colors
|
||||
.iter()
|
||||
.fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b)
|
||||
/ self.colors.len() as f32;
|
||||
color.y
|
||||
.fold(0.0, |a, &b| a + b.approximate_energy())
|
||||
/ self.colors.len() as f32
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use mem_arena::MemArena;
|
|||
use crate::{
|
||||
bbox::BBox,
|
||||
boundable::Boundable,
|
||||
color::{Color, SpectralSample, XYZ},
|
||||
color::{Color, SpectralSample},
|
||||
lerp::lerp_slice,
|
||||
math::{coordinate_system_from_vector, dot, Matrix4x4, Normal, Point, Vector},
|
||||
ray::{AccelRay, Ray},
|
||||
|
@ -26,12 +26,12 @@ const SAMPLE_POINT_FUDGE: f32 = 0.001;
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct SphereLight<'a> {
|
||||
radii: &'a [f32],
|
||||
colors: &'a [XYZ],
|
||||
colors: &'a [Color],
|
||||
bounds_: &'a [BBox],
|
||||
}
|
||||
|
||||
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
|
||||
.iter()
|
||||
.map(|r| BBox {
|
||||
|
@ -164,7 +164,7 @@ impl<'a> SurfaceLight for SphereLight<'a> {
|
|||
)
|
||||
};
|
||||
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 (
|
||||
spectral_sample,
|
||||
(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 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 (
|
||||
spectral_sample,
|
||||
(sample_point, normal, sample_point_err),
|
||||
|
@ -196,12 +196,10 @@ impl<'a> SurfaceLight for SphereLight<'a> {
|
|||
}
|
||||
|
||||
fn approximate_energy(&self) -> f32 {
|
||||
let color: XYZ = self
|
||||
.colors
|
||||
self.colors
|
||||
.iter()
|
||||
.fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b)
|
||||
/ self.colors.len() as f32;
|
||||
color.y
|
||||
.fold(0.0, |a, &b| a + b.approximate_energy())
|
||||
/ self.colors.len() as f32
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use mem_arena::MemArena;
|
|||
|
||||
use crate::{
|
||||
camera::Camera,
|
||||
color::{rec709_e_to_xyz, XYZ},
|
||||
color::{rec709_e_to_xyz, Color},
|
||||
light::WorldLightSource,
|
||||
math::Matrix4x4,
|
||||
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
|
||||
// rec.709.
|
||||
background_color = XYZ::from_tuple(rec709_e_to_xyz(color));
|
||||
background_color = Color::new_xyz(rec709_e_to_xyz(color));
|
||||
} else {
|
||||
return Err(PsyParseError::IncorrectLeafData(
|
||||
byte_offset,
|
||||
|
|
|
@ -7,7 +7,7 @@ use nom::{call, closure, tuple, tuple_parser, IResult};
|
|||
use mem_arena::MemArena;
|
||||
|
||||
use crate::{
|
||||
color::{rec709_e_to_xyz, XYZ},
|
||||
color::{rec709_e_to_xyz, Color},
|
||||
light::{DistantDiskLight, RectangleLight, SphereLight},
|
||||
math::Vector,
|
||||
};
|
||||
|
@ -68,7 +68,7 @@ pub fn parse_distant_disk_light<'a>(
|
|||
// TODO: handle color space conversions properly.
|
||||
// Probably will need a special color type with its
|
||||
// own parser...?
|
||||
colors.push(XYZ::from_tuple(rec709_e_to_xyz(color)));
|
||||
colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
|
||||
} else {
|
||||
// Found color, but its contents is not in the right format
|
||||
return Err(PsyParseError::UnknownError(byte_offset));
|
||||
|
@ -122,7 +122,7 @@ pub fn parse_sphere_light<'a>(
|
|||
// TODO: handle color space conversions properly.
|
||||
// Probably will need a special color type with its
|
||||
// own parser...?
|
||||
colors.push(XYZ::from_tuple(rec709_e_to_xyz(color)));
|
||||
colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
|
||||
} else {
|
||||
// Found color, but its contents is not in the right format
|
||||
return Err(PsyParseError::UnknownError(byte_offset));
|
||||
|
@ -178,7 +178,7 @@ pub fn parse_rectangle_light<'a>(
|
|||
// TODO: handle color space conversions properly.
|
||||
// Probably will need a special color type with its
|
||||
// own parser...?
|
||||
colors.push(XYZ::from_tuple(rec709_e_to_xyz(color)));
|
||||
colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
|
||||
} else {
|
||||
// Found color, but its contents is not in the right format
|
||||
return Err(PsyParseError::UnknownError(byte_offset));
|
||||
|
|
|
@ -7,7 +7,7 @@ use nom::{call, closure, tuple, tuple_parser, IResult};
|
|||
use mem_arena::MemArena;
|
||||
|
||||
use crate::{
|
||||
color::{rec709_e_to_xyz, XYZ},
|
||||
color::{rec709_e_to_xyz, Color},
|
||||
shading::{SimpleSurfaceShader, SurfaceShader},
|
||||
};
|
||||
|
||||
|
@ -44,7 +44,7 @@ pub fn parse_surface_shader<'a>(
|
|||
// TODO: handle color space conversions properly.
|
||||
// Probably will need a special color type with its
|
||||
// own parser...?
|
||||
XYZ::from_tuple(rec709_e_to_xyz(color))
|
||||
Color::new_xyz(rec709_e_to_xyz(color))
|
||||
} else {
|
||||
// Found color, but its contents is not in the right format
|
||||
return Err(PsyParseError::UnknownError(byte_offset));
|
||||
|
@ -68,7 +68,7 @@ pub fn parse_surface_shader<'a>(
|
|||
// TODO: handle color space conversions properly.
|
||||
// Probably will need a special color type with its
|
||||
// own parser...?
|
||||
XYZ::from_tuple(rec709_e_to_xyz(color))
|
||||
Color::new_xyz(rec709_e_to_xyz(color))
|
||||
} else {
|
||||
// Found color, but its contents is not in the right format
|
||||
return Err(PsyParseError::UnknownError(byte_offset));
|
||||
|
@ -93,7 +93,7 @@ pub fn parse_surface_shader<'a>(
|
|||
// TODO: handle color space conversions properly.
|
||||
// Probably will need a special color type with its
|
||||
// own parser...?
|
||||
XYZ::from_tuple(rec709_e_to_xyz(color))
|
||||
Color::new_xyz(rec709_e_to_xyz(color))
|
||||
} else {
|
||||
// Found color, but its contents is not in the right format
|
||||
return Err(PsyParseError::UnknownError(byte_offset));
|
||||
|
@ -172,7 +172,7 @@ pub fn parse_surface_shader<'a>(
|
|||
// TODO: handle color space conversions properly.
|
||||
// Probably will need a special color type with its
|
||||
// own parser...?
|
||||
XYZ::from_tuple(rec709_e_to_xyz(color))
|
||||
Color::new_xyz(rec709_e_to_xyz(color))
|
||||
} else {
|
||||
// Found color, but its contents is not in the right format
|
||||
return Err(PsyParseError::UnknownError(byte_offset));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{color::XYZ, light::WorldLightSource};
|
||||
use crate::{color::Color, light::WorldLightSource};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct World<'a> {
|
||||
pub background_color: XYZ,
|
||||
pub background_color: Color,
|
||||
pub lights: &'a [&'a WorldLightSource],
|
||||
}
|
||||
|
|
|
@ -2,10 +2,7 @@ pub mod surface_closure;
|
|||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::{
|
||||
color::{Color, XYZ},
|
||||
surface::SurfaceIntersectionData,
|
||||
};
|
||||
use crate::{color::Color, surface::SurfaceIntersectionData};
|
||||
|
||||
use self::surface_closure::{
|
||||
EmitClosure, GGXClosure, GTRClosure, LambertClosure, SurfaceClosureUnion,
|
||||
|
@ -37,19 +34,19 @@ pub trait SurfaceShader: Debug + Sync {
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SimpleSurfaceShader {
|
||||
Emit {
|
||||
color: XYZ,
|
||||
color: Color,
|
||||
},
|
||||
Lambert {
|
||||
color: XYZ,
|
||||
color: Color,
|
||||
},
|
||||
GTR {
|
||||
color: XYZ,
|
||||
color: Color,
|
||||
roughness: f32,
|
||||
tail_shape: f32,
|
||||
fresnel: f32,
|
||||
},
|
||||
GGX {
|
||||
color: XYZ,
|
||||
color: Color,
|
||||
roughness: f32,
|
||||
fresnel: f32,
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::iter;
|
|||
|
||||
use crate::{
|
||||
algorithm::partition,
|
||||
color::{rec709_to_xyz, XYZ},
|
||||
color::{rec709_to_xyz, Color},
|
||||
lerp::lerp_slice,
|
||||
ray::{AccelRay, Ray},
|
||||
scene::{Assembly, InstanceType, Object},
|
||||
|
@ -182,7 +182,7 @@ impl<'a> TracerInner<'a> {
|
|||
match *obj {
|
||||
Object::Surface(surface) => {
|
||||
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);
|
||||
|
||||
|
@ -198,7 +198,7 @@ impl<'a> TracerInner<'a> {
|
|||
Object::SurfaceLight(surface) => {
|
||||
// Lights don't use shaders
|
||||
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(
|
||||
|
|
Loading…
Reference in New Issue
Block a user