Skip to content

Commit

Permalink
improvement: better color handling with XYZ and sRGB
Browse files Browse the repository at this point in the history
  • Loading branch information
Walther committed Oct 3, 2023
1 parent 9eeb85c commit 5fe3d03
Show file tree
Hide file tree
Showing 26 changed files with 230 additions and 92 deletions.
2 changes: 1 addition & 1 deletion clovers-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct Opts {
#[clap(short = 'd', long, default_value = "100")]
max_depth: u32,
/// Gamma correction value
#[clap(short, long, default_value = "2.0")]
#[clap(short, long, default_value = "2.2")]
gamma: Float,
/// Suppress most of the text output
#[clap(short, long)]
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/bvhnode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use rand::{rngs::SmallRng, Rng};

use crate::{
aabb::AABB,
colors::Wavelength,
hitable::{Empty, HitRecord, Hitable, HitableTrait},
ray::Ray,
spectral::Wavelength,
Box, Float, Vec, Vec3,
};

Expand Down
2 changes: 1 addition & 1 deletion clovers/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![allow(clippy::too_many_arguments)] // TODO: Camera::new() has a lot of arguments.

use crate::spectral::random_wavelength;
use crate::colors::random_wavelength;
use crate::{random::random_in_unit_disk, ray::Ray, Float, Vec3, PI};
use rand::rngs::SmallRng;
use rand::Rng;
Expand Down
9 changes: 9 additions & 0 deletions clovers/src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// TODO: more flexible colors?

use crate::colors::sRGB;
use crate::{Float, Vec3};
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign};
use rand::rngs::SmallRng;
Expand Down Expand Up @@ -200,3 +201,11 @@ impl Div<Float> for Color {
Color::new(self.r / rhs, self.g / rhs, self.b / rhs)
}
}

impl From<sRGB> for Color {
fn from(value: sRGB) -> Self {
// TODO: verify correctness / possibly remove the simplistic `Color` type
let sRGB { r, g, b } = value;
Color { r, g, b }
}
}
8 changes: 7 additions & 1 deletion clovers/src/colorize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use crate::{
color::Color,
colors::{sRGB, sRGB_Linear, XYZ_Normalized, XYZ_Tristimulus},
hitable::HitableTrait,
materials::MaterialType,
pdf::{HitablePDF, MixturePDF, PDFTrait, PDF},
Expand Down Expand Up @@ -29,7 +30,12 @@ pub fn colorize(ray: &Ray, scene: &Scene, depth: u32, max_depth: u32, rng: &mut
};

// Spectral rendering: compute a tint based on the current ray's wavelength
let tint: Color = ray.wavelength.into();
// TODO: all color handling in XYZ space?
let tint: XYZ_Tristimulus = ray.wavelength.into();
let tint: XYZ_Normalized = tint.into();
let tint: sRGB_Linear = tint.into();
let tint: sRGB = tint.into();
let tint: Color = tint.into();

// Get the emitted color from the surface that we just hit
let mut emitted: Color = hit_record.material.emit(
Expand Down
9 changes: 9 additions & 0 deletions clovers/src/colors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Color utilities.
pub mod photon;
pub mod rgb;
pub mod xyz;

pub use photon::*;
pub use rgb::*;
pub use xyz::*;
38 changes: 38 additions & 0 deletions clovers/src/colors/photon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! The fundamental building blocks of spectral rendering.
use core::{array::from_fn, ops::Range};
use rand::rngs::SmallRng;
use rand_distr::uniform::SampleRange;

/// A fundamental light particle.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
pub struct Photon {
/// Wavelength of the photon
pub wavelength: Wavelength,
// TODO: spin for polarization
// pub spin: bool,
}

/// Wavelength in nanometers
pub type Wavelength = usize;

const MIN_WAVELENGTH: Wavelength = 380;
const MAX_WAVELENGTH: Wavelength = 780;
const SPECTRUM: Range<Wavelength> = MIN_WAVELENGTH..MAX_WAVELENGTH;
const SPECTRUM_SIZE: usize = MAX_WAVELENGTH - MIN_WAVELENGTH;
const WAVE_SAMPLE_COUNT: usize = 4;

/// Return a random wavelength, sampled uniformly from the visible spectrum.
pub fn random_wavelength(rng: &mut SmallRng) -> Wavelength {
SPECTRUM.sample_single(rng)
}

/// Given a hero wavelength, create additional equidistant wavelengths in the visible spectrum. Returns an array of wavelengths, with the original hero wavelength as the first one.
#[must_use]
pub fn rotate_wavelength(hero: Wavelength) -> [Wavelength; WAVE_SAMPLE_COUNT] {
from_fn(|j| {
(hero - MIN_WAVELENGTH + ((1 + j) / WAVE_SAMPLE_COUNT) * SPECTRUM_SIZE)
% (SPECTRUM_SIZE + MIN_WAVELENGTH)
})
}
70 changes: 70 additions & 0 deletions clovers/src/colors/rgb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! RGB colorspace utilities.
use crate::Float;

use super::XYZ_Normalized;

/// Linear `sRGB` color based on three [Floats](crate::Float) values.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
#[allow(non_camel_case_types)]
pub struct sRGB_Linear {
/// The red component of the color, as a [Float]
pub r: Float,
/// The green component of the color, as a [Float]
pub g: Float,
/// The blue component of the color, as a [Float]
pub b: Float,
}

/// Conversion from XYZ (D65) to linear `sRGB` values <https://color.org/chardata/rgb/sRGB.pdf>
impl From<XYZ_Normalized> for sRGB_Linear {
fn from(value: XYZ_Normalized) -> Self {
let XYZ_Normalized { x, y, z } = value;
let r = 3.240_625_5 * x - 1.537_208 * y - 0.498_628_6 * z;
let g = -0.968_930_7 * x + 1.875_756_1 * y + 0.041_517_5 * z;
let b = 0.055_710_1 * x - 0.204_021_1 * y + 1.056_995_9 * z;

let r = r.clamp(0.0, 1.0);
let g = g.clamp(0.0, 1.0);
let b = b.clamp(0.0, 1.0);

sRGB_Linear { r, g, b }
}
}

/// Color component transfer function.
/// Note: Produces `sRGB` digital values with a range 0 to 1, which must then be multiplied by 2^(bit depth) – 1 and quantized.
/// <https://color.org/chardata/rgb/sRGB.pdf>
#[must_use]
pub fn color_component_transfer(c: Float) -> Float {
if c.abs() < 0.003_130_8 {
12.92 * c
} else {
1.055 * c.powf(1.0 / 2.4) - 0.055
}
}

/// Gamma-corrected `sRGB` color based on three [Floats](crate::Float) values.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
#[allow(non_camel_case_types)]
pub struct sRGB {
/// The red component of the color, as a [Float]
pub r: Float,
/// The green component of the color, as a [Float]
pub g: Float,
/// The blue component of the color, as a [Float]
pub b: Float,
}

impl From<sRGB_Linear> for sRGB {
fn from(value: sRGB_Linear) -> Self {
let sRGB_Linear { r, g, b } = value;
sRGB {
r: color_component_transfer(r),
g: color_component_transfer(g),
b: color_component_transfer(b),
}
}
}
78 changes: 78 additions & 0 deletions clovers/src/colors/xyz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! CIE 1931 XYZ colorspace utilities.
use crate::Float;

use super::Wavelength;

/// CIE 1931 XYZ Tristimulus color based on three [Floats](crate::Float) values.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
#[allow(non_camel_case_types)]
pub struct XYZ_Tristimulus {
/// The x component of the color, as a [Float]
pub x: Float,
/// The y component of the color, as a [Float]
pub y: Float,
/// The z component of the color, as a [Float]
pub z: Float,
}

/// Helper function adapted from <https://en.wikipedia.org/wiki/CIE_1931_color_space#Analytical_approximation>
fn gaussian(x: Float, alpha: Float, mu: Float, sigma1: Float, sigma2: Float) -> Float {
let t = (x - mu) / (if x < mu { sigma1 } else { sigma2 });
alpha * (-(t * t) / 2.0).exp()
}

/// Helper function adapted from <https://en.wikipedia.org/wiki/CIE_1931_color_space#Analytical_approximation>
impl From<Wavelength> for XYZ_Tristimulus {
// TODO: precision loss
#[allow(clippy::cast_precision_loss)]
fn from(lambda: Wavelength) -> Self {
// With the wavelength λ measured in nanometers, we then approximate the 1931 color matching functions:
let l: Float = lambda as Float;
let x = 0.0 // for readability of next lines
+ gaussian(l, 1.056, 599.8, 37.9, 31.0)
+ gaussian(l, 0.362, 442.0, 16.0, 26.7)
+ gaussian(l, -0.065, 501.1, 20.4, 26.2);
let y = gaussian(l, 0.821, 568.8, 46.9, 40.5) + gaussian(l, 0.286, 530.9, 16.3, 31.1);
let z = gaussian(l, 1.217, 437.0, 11.8, 36.0) + gaussian(l, 0.681, 459.0, 26.0, 13.8);

XYZ_Tristimulus { x, y, z }
}
}

/// CIE 1931 XYZ Normalized color based on three [Floats](crate::Float) values.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))]
#[allow(non_camel_case_types)]
pub struct XYZ_Normalized {
/// The x component of the color, as a [Float]
pub x: Float,
/// The y component of the color, as a [Float]
pub y: Float,
/// The z component of the color, as a [Float]
pub z: Float,
}

/// Tristimulus value normalization. <https://color.org/chardata/rgb/sRGB.pdf>
impl From<XYZ_Tristimulus> for XYZ_Normalized {
fn from(value: XYZ_Tristimulus) -> Self {
let XYZ_Tristimulus { x, y, z } = value;
// TODO: why does this normalization make the image bad? It should be needed?

// let x_n = (76.04 * (x - 0.1901)) / (80.0 * (76.04 - 0.1901));
// let y_n = (y - 0.2) / (80.0 - 0.2);
// let z_n = (87.12 * (z - 0.2178)) / (80.0 * (87.12 - 0.2178));

// FIXME: normalization not done, but image looks more correct?
let x_n = x;
let y_n = y;
let z_n = z;

XYZ_Normalized {
x: x_n,
y: y_n,
z: z_n,
}
}
}
2 changes: 1 addition & 1 deletion clovers/src/hitable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ use crate::objects::{GLTFTriangle, GLTF};
use crate::{
aabb::AABB,
bvhnode::BVHNode,
colors::Wavelength,
materials::MaterialTrait,
objects::{
Boxy, ConstantMedium, FlipFace, MovingSphere, Quad, RotateY, Sphere, Translate, Triangle,
},
ray::Ray,
spectral::Wavelength,
Float, Vec3,
};

Expand Down
2 changes: 1 addition & 1 deletion clovers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub mod bvhnode;
pub mod camera;
pub mod color;
pub mod colorize;
pub mod colors;
pub mod hitable;
pub mod interval;
pub mod materials;
Expand All @@ -89,7 +90,6 @@ pub mod pdf;
pub mod random;
pub mod ray;
pub mod scenes;
pub mod spectral;
pub mod textures;

/// Rendering options struct
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/materials/dispersive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ use rand::{rngs::SmallRng, Rng};

use crate::{
color::Color,
colors::Wavelength,
hitable::HitRecord,
pdf::{ZeroPDF, PDF},
ray::Ray,
spectral::Wavelength,
Float, Vec3,
};

Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/boxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
use super::Quad;
use crate::{
aabb::AABB,
colors::Wavelength,
hitable::{HitRecord, Hitable, HitableTrait},
materials::{Material, MaterialInit},
ray::Ray,
spectral::Wavelength,
Box, Float, Vec3,
};
use rand::{rngs::SmallRng, Rng};
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/constant_medium.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
use crate::{
aabb::AABB,
colors::Wavelength,
hitable::{HitRecord, Hitable, HitableTrait},
materials::{isotropic::Isotropic, Material},
random::random_unit_vector,
ray::Ray,
spectral::Wavelength,
textures::Texture,
Box, Float, Vec3, EPSILON_CONSTANT_MEDIUM,
};
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/flip_face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
use crate::{
aabb::AABB,
colors::Wavelength,
hitable::{HitRecord, Hitable, HitableTrait},
ray::Ray,
spectral::Wavelength,
Box, Float, Vec3,
};
use rand::rngs::SmallRng;
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ use tracing::debug;
use crate::{
aabb::AABB,
bvhnode::BVHNode,
colors::Wavelength,
hitable::{get_orientation, HitRecord, Hitable, HitableTrait},
interval::Interval,
materials::gltf::GLTFMaterial,
ray::Ray,
spectral::Wavelength,
Float, Vec3, EPSILON_RECT_THICKNESS, EPSILON_SHADOW_ACNE,
};

Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/moving_sphere.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
use crate::{
aabb::AABB,
colors::Wavelength,
hitable::{HitRecord, HitableTrait},
materials::{Material, MaterialInit},
random::random_in_unit_sphere,
ray::Ray,
spectral::Wavelength,
Float, Vec3, PI,
};
use rand::rngs::SmallRng;
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/quad.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! A quadrilateral object.
// TODO: better docs

use crate::colors::Wavelength;
use crate::hitable::HitableTrait;
use crate::materials::MaterialInit;
use crate::spectral::Wavelength;
use crate::EPSILON_SHADOW_ACNE;
use crate::{
aabb::AABB, hitable::get_orientation, hitable::HitRecord, materials::Material, ray::Ray, Float,
Expand Down
2 changes: 1 addition & 1 deletion clovers/src/objects/rotate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
use crate::{
aabb::AABB,
colors::Wavelength,
hitable::{HitRecord, Hitable, HitableTrait},
ray::Ray,
spectral::Wavelength,
Box, Float, Vec3,
};
use rand::rngs::SmallRng;
Expand Down
Loading

0 comments on commit 5fe3d03

Please sign in to comment.