use std::sync::Arc;
use nanorand::tls::TlsWyRand;
use crate::color::{Color, Texture};
use crate::materials::{compute_color, compute_normal, Material};
use crate::math::{Hit, Point2, Ray, Vec3};
use crate::prelude::IOR;
use crate::util::random_float;
#[derive(Clone)]
pub struct PbrMaterial {
pub base_color: Color,
pub base_color_texture: Option<Arc<dyn Texture>>,
pub metallic: f64,
pub roughness: f64,
pub metallic_roughness_texture: Option<Arc<dyn Texture>>,
pub transmission: f64,
pub ior: f64,
pub emissive_color: Color,
pub emissive_texture: Option<Arc<dyn Texture>>,
pub emissive_strength: f64,
pub normal_texture: Option<Arc<dyn Texture>>,
}
impl Default for PbrMaterial {
fn default() -> Self {
Self {
base_color: Color::WHITE,
base_color_texture: None,
metallic: 0.0,
roughness: 0.0,
metallic_roughness_texture: None,
transmission: 0.0,
ior: IOR::GLASS,
emissive_color: Color::BLACK,
emissive_texture: None,
emissive_strength: 1.0,
normal_texture: None,
}
}
}
impl PbrMaterial {
fn normal(&self, hit: Hit) -> Vec3 {
match hit.tangent {
None => hit.normal,
Some(tangent) => {
let normal = hit.normal;
let bitangent = Vec3::cross(normal, tangent.xyz()) * tangent.w;
compute_normal(
normal,
tangent.xyz(),
bitangent,
&self.normal_texture,
hit.uv,
)
}
}
}
fn next_ray_diffuse(&self, hit: Hit, rng: &mut TlsWyRand) -> Option<Ray> {
let direction = self.normal(hit) + Vec3::random_unit_vector(rng);
Some(Ray::new(hit.position, direction, IOR::AIR))
}
fn next_ray_metallic(&self, ray: Ray, hit: Hit, rng: &mut TlsWyRand) -> Option<Ray> {
let direction = ray.direction.reflect(self.normal(hit));
let roughness = compute_color(
Color::WHITE,
&self.metallic_roughness_texture,
self.roughness,
hit.uv,
)
.g as f64;
Some(Ray::new(
hit.position,
direction + roughness * Vec3::random_in_unit_sphere(rng),
IOR::AIR,
))
}
fn next_ray_refractive(&self, ray: Ray, hit: Hit, rng: &mut TlsWyRand) -> Option<Ray> {
let (n1, n2) = (ray.last_ior, self.ior);
let direction = ray.direction.refract(self.normal(hit), n1, n2, rng);
Some(Ray::new(
hit.position,
direction + self.roughness * Vec3::random_in_unit_sphere(rng),
self.ior,
))
}
}
impl Material for PbrMaterial {
fn next_ray(&self, ray: Ray, hit: Hit, rng: &mut TlsWyRand) -> Option<Ray> {
let metallic = compute_color(
Color::WHITE,
&self.metallic_roughness_texture,
self.metallic,
hit.uv,
)
.b as f64;
if random_float(rng, 0.0, 1.0) < metallic {
self.next_ray_metallic(ray, hit, rng)
} else if random_float(rng, 0.0, 1.0) < self.transmission {
self.next_ray_refractive(ray, hit, rng)
} else {
self.next_ray_diffuse(hit, rng)
}
}
fn get_color(&self, uv: Point2) -> (Color, Color) {
(
compute_color(self.base_color, &self.base_color_texture, 1.0, uv),
compute_color(
self.emissive_color,
&self.emissive_texture,
self.emissive_strength,
uv,
),
)
}
}