use super::{Vec3, vec3};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Material {
pub base_color: Vec3,
pub emission: Vec3,
pub roughness: f32,
pub metallic: f32,
pub transmission: f32,
pub ior: f32,
}
impl Material {
pub fn diffuse(color: Vec3) -> Self {
Self {
base_color: color,
emission: Vec3::ZERO,
roughness: 1.0,
metallic: 0.0,
transmission: 0.0,
ior: 1.5,
}
.sanitized()
}
pub fn metal(color: Vec3, roughness: f32) -> Self {
Self {
base_color: color,
emission: Vec3::ZERO,
roughness,
metallic: 1.0,
transmission: 0.0,
ior: 1.5,
}
.sanitized()
}
pub fn dielectric(color: Vec3, ior: f32) -> Self {
Self {
base_color: color,
emission: Vec3::ZERO,
roughness: 0.02,
metallic: 0.0,
transmission: 1.0,
ior,
}
.sanitized()
}
pub fn emissive(color: Vec3, strength: f32) -> Self {
Self {
base_color: Vec3::ZERO,
emission: color * finite_or(strength, 0.0).max(0.0),
roughness: 1.0,
metallic: 0.0,
transmission: 0.0,
ior: 1.5,
}
.sanitized()
}
pub fn sanitized(self) -> Self {
let metallic = finite_unit(self.metallic);
Self {
base_color: finite_unit_color(self.base_color),
emission: finite_positive_color(self.emission),
roughness: finite_or(self.roughness, 1.0).clamp(0.02, 1.0),
metallic,
transmission: finite_unit(self.transmission) * (1.0 - metallic),
ior: finite_or(self.ior, 1.5).clamp(1.0001, 3.0),
}
}
}
impl Default for Material {
fn default() -> Self {
Self::diffuse(vec3(0.8, 0.8, 0.8))
}
}
const fn finite_or(value: f32, fallback: f32) -> f32 {
if value.is_finite() { value } else { fallback }
}
const fn finite_unit(value: f32) -> f32 {
finite_or(value, 0.0).clamp(0.0, 1.0)
}
const fn finite_unit_color(color: Vec3) -> Vec3 {
vec3(finite_unit(color.x), finite_unit(color.y), finite_unit(color.z))
}
fn finite_positive_color(color: Vec3) -> Vec3 {
let channel = |value: f32| {
if value.is_finite() {
value.max(0.0)
} else {
0.0
}
};
vec3(channel(color.x), channel(color.y), channel(color.z))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitization_bounds_scattering_energy_and_keeps_hdr_emission() {
let material = Material {
base_color: vec3(-1.0, 2.0, f32::NAN),
emission: vec3(4.0, -2.0, f32::INFINITY),
roughness: 0.0,
metallic: 0.75,
transmission: 1.0,
ior: 99.0,
}
.sanitized();
assert_eq!(material.base_color, vec3(0.0, 1.0, 0.0));
assert_eq!(material.emission, vec3(4.0, 0.0, 0.0));
assert_eq!(material.roughness, 0.02);
assert_eq!(material.transmission, 0.25);
assert_eq!(material.ior, 3.0);
}
}