Schlick Approximation
Now real glass has reflectivity that varies with angle — look at a window at a steep angle and it becomes a mirror. There is a big ugly equation for that, but almost everybody uses a cheap and surprisingly accurate polynomial approximation by Christophe Schlick. This yields our full glass material:
diff --git a/src/material.rs b/src/material.rs
index 951f550..20477f9 100644
--- a/src/material.rs
+++ b/src/material.rs
@@ -1,97 +1,104 @@
use crate::{hittable::HitRecord, prelude::*};
pub trait Material {
fn scatter(&self, _r_in: Ray, _rec: HitRecord) -> Option<(Ray, Color)> {
None
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Lambertian {
albedo: Color,
}
impl Lambertian {
pub fn new(albedo: Color) -> Self {
Self { albedo }
}
}
impl Material for Lambertian {
fn scatter(&self, _r_in: Ray, rec: HitRecord) -> Option<(Ray, Color)> {
let mut scatter_direction = rec.normal + random_unit_vector();
// Catch degenerate scatter direction
if scatter_direction.near_zero() {
scatter_direction = rec.normal;
}
let scattered = Ray::new(rec.p, scatter_direction);
let attenuation = self.albedo;
Some((scattered, attenuation))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Metal {
albedo: Color,
fuzz: f64,
}
impl Metal {
pub fn new(albedo: Color, fuzz: f64) -> Self {
let fuzz = if fuzz < 1.0 { fuzz } else { 1.0 };
Self { albedo, fuzz }
}
}
impl Material for Metal {
fn scatter(&self, r_in: Ray, rec: HitRecord) -> Option<(Ray, Color)> {
let mut reflected = reflect(r_in.direction(), rec.normal);
reflected = unit_vector(reflected) + (self.fuzz * random_unit_vector());
let scattered = Ray::new(rec.p, reflected);
let attenuation = self.albedo;
(dot(scattered.direction(), rec.normal) > 0.0).then(|| (scattered, attenuation))
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Dielectric {
/// Refractive index in vacuum or air, or the ratio of the material's refractive index over
/// the refractive index of the enclosing media
refraction_index: f64,
}
impl Dielectric {
pub fn new(refraction_index: f64) -> Self {
Self { refraction_index }
}
+
+ fn reflectance(cosine: f64, refraction_index: f64) -> f64 {
+ let r0 = (1.0 - refraction_index) / (1.0 + refraction_index);
+ let r0 = r0 * r0;
+ r0 + (1.0 - r0) * f64::powi(1.0 - cosine, 5)
+ }
}
impl Material for Dielectric {
fn scatter(&self, r_in: Ray, rec: HitRecord) -> Option<(Ray, Color)> {
let attenuation = Color::new(1.0, 1.0, 1.0);
let ri = if rec.front_face {
1.0 / self.refraction_index
} else {
self.refraction_index
};
let unit_direction = unit_vector(r_in.direction());
let cos_theta = f64::min(dot(-unit_direction, rec.normal), 1.0);
let sin_theta = f64::sqrt(1.0 - cos_theta * cos_theta);
let cannot_refract = ri * sin_theta > 1.0;
- let direction = if cannot_refract {
+ let direction = if cannot_refract || Dielectric::reflectance(cos_theta, ri) > rand::random()
+ {
reflect(unit_direction, rec.normal)
} else {
refract(unit_direction, rec.normal, ri)
};
let scattered = Ray::new(rec.p, direction);
Some((scattered, attenuation))
}
}
Listing 78: [material.rs] Full glass material