Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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