use crate::math::Float;
pub trait Tonemapper: 'static {}
pub trait Tonemap<T>: Tonemapper {
fn tonemap(v: T) -> T;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AcesNarkowicz;
impl Tonemapper for AcesNarkowicz {}
impl<F: Float> Tonemap<F> for AcesNarkowicz {
#[inline(always)]
fn tonemap(x: F) -> F {
let a = F::from_f64(2.51);
let b = F::from_f64(0.03);
let c = F::from_f64(2.43);
let d = F::from_f64(0.59);
let e = F::from_f64(0.14);
let zero = F::ZERO;
let x = x.max(zero);
(x * (a * x + b)) / (x * (c * x + d) + e)
}
}
impl<F: Float> Tonemap<[F; 3]> for AcesNarkowicz {
#[inline(always)]
fn tonemap(v: [F; 3]) -> [F; 3] {
[
Self::tonemap(v[0]),
Self::tonemap(v[1]),
Self::tonemap(v[2]),
]
}
}
impl<F: Float> Tonemap<[F; 4]> for AcesNarkowicz {
#[inline(always)]
fn tonemap(v: [F; 4]) -> [F; 4] {
[
Self::tonemap(v[0]),
Self::tonemap(v[1]),
Self::tonemap(v[2]),
v[3],
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Reinhard;
impl Tonemapper for Reinhard {}
impl<F: Float> Tonemap<F> for Reinhard {
#[inline(always)]
fn tonemap(x: F) -> F {
let x = x.max(F::ZERO);
x / (x + F::ONE)
}
}
impl<F: Float> Tonemap<[F; 3]> for Reinhard {
#[inline(always)]
fn tonemap(v: [F; 3]) -> [F; 3] {
[
Self::tonemap(v[0]),
Self::tonemap(v[1]),
Self::tonemap(v[2]),
]
}
}
impl<F: Float> Tonemap<[F; 4]> for Reinhard {
#[inline(always)]
fn tonemap(v: [F; 4]) -> [F; 4] {
[
Self::tonemap(v[0]),
Self::tonemap(v[1]),
Self::tonemap(v[2]),
v[3],
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct KhronosPbrNeutral;
impl Tonemapper for KhronosPbrNeutral {}
impl<F: Float> Tonemap<[F; 3]> for KhronosPbrNeutral {
#[inline(always)]
fn tonemap(color: [F; 3]) -> [F; 3] {
let start_compression = F::from_f64(0.8 - 0.04);
let desaturation = F::from_f64(0.15);
let mut r = color[0];
let mut g = color[1];
let mut b = color[2];
let x = r.min(g).min(b);
let offset = if x < F::from_f64(0.08) {
x - F::from_f64(6.25) * x * x
} else {
F::from_f64(0.04)
};
r = r - offset;
g = g - offset;
b = b - offset;
let peak = r.max(g).max(b);
if peak < start_compression {
return [r, g, b];
}
let d = F::ONE - start_compression;
let new_peak = F::ONE - d * d / (peak + d - start_compression);
let scale = new_peak / peak;
r = r * scale;
g = g * scale;
b = b * scale;
let g_mix = F::ONE - F::ONE / (desaturation * (peak - new_peak) + F::ONE);
let mix = |base: F, target: F, alpha: F| -> F { base * (F::ONE - alpha) + target * alpha };
[
mix(r, new_peak, g_mix),
mix(g, new_peak, g_mix),
mix(b, new_peak, g_mix),
]
}
}
impl<F: Float> Tonemap<[F; 4]> for KhronosPbrNeutral {
#[inline(always)]
fn tonemap(v: [F; 4]) -> [F; 4] {
let [r, g, b] = Self::tonemap([v[0], v[1], v[2]]);
[r, g, b, v[3]]
}
}