rtwlib/
material.rs

1//! `materials` is a collection of types that implement the `Material` trait.
2//! materials are associated with objects, and determine how it interacts with light
3//! Every material has a `scatter` function, which takes an input ray and a hit record, and returns a boolean indicating if the ray was scattered, and modifies the input variables to reflect the scattered ray, colors, and other properties.
4//! The available materials are:
5//! - [`Lambertian`]: A diffuse material, effectively reflects light in a random direction, with a color determined by the albedo.
6//! - [`Normal`]: A material that colors the object based on the normal vector at the hit point, mostly a joke, just a fancy colored lambertian.
7//! - [`Metal`]: A material that reflects light. The reflectance is determined by the fuzziness of the material, with higher
8use std::fmt::Debug;
9
10use rand::Rng;
11
12use crate::{color::Color, hittable::HitRecord, ray::Ray, vec3::*};
13
14/// A `Material` is a trait that represents a material that can be applied to an object. This requires the `scatter` function to be implemented, which describes how the material scatters an incoming ray.
15///
16pub trait Material: Debug {
17    /// Given an incoming ray and a hit record, this function should return a boolean indicating if the ray was scattered, and modify the input variables to reflect the scattered ray, colors, and other properties.
18    /// # Arguments
19    /// * `r_in` - The incoming ray
20    /// * `rec` - A [`HitRecord`] ( stores location, normal, material,  and other information about the hit )
21    /// * `attenuation` - The color of incoming light ray, to be modified by the material
22    /// * `scattered` - The scattered ray, to be modified by the material
23    fn scatter(
24        &self,
25        _r_in: &Ray,
26        _rec: &HitRecord,
27        _attenuation: &mut Color,
28        _scattered: &mut Ray,
29    ) -> bool {
30        false
31    }
32    /// Returns a string representation of the material, for debugging purposes.
33    fn as_string(&self) -> String {
34        format!("{:?}", self)
35    }
36}
37
38#[derive(Debug)]
39/// A diffuse material, scatters light at random, with a color. It models a perfectly matte surface.
40/// The `albedo` is the color of the material.
41/// This has the most vibrarnt color of all the materials, as it reflects light in all directions.
42pub struct Lambertian {
43    albedo: Color,
44}
45#[derive(Debug)]
46/// Almost Identical to the lambertian, but the color is dynamically determined by the normal vector at the hit point.
47pub struct Normal {}
48#[derive(Debug)]
49/// A metal material, reflects light and imparts a slight color.
50/// The reflectance is determined by the fuzziness of the material, with higher values being more blurry, don't use negative values unless you want some weird results.
51/// The `albedo` is the color of the material, this generally looks like a tint of the reflected light.
52pub struct Metal {
53    albedo: Color,
54    fuzz: f64, //I could enforce a specific range, buts its funnier not to.
55}
56#[derive(Debug)]
57/// A dielectric material, refracts light, basically glass.
58pub struct Dielectric {
59    ior: f64,
60}
61
62impl Metal {
63    /// Creates a new `Metal` material with the given albedo and fuzziness.
64    pub fn new(albedo: Color, fuzz: f64) -> Self {
65        Metal { albedo, fuzz }
66    }
67}
68impl Lambertian {
69    /// Creates a new `Lambertian` material with the given albedo.
70    pub fn new(albedo: Color) -> Self {
71        Lambertian { albedo }
72    }
73}
74impl Dielectric {
75    /// Creates a new `Dielectric` material with the given index of refraction.
76    pub fn new(ior: f64) -> Self {
77        Dielectric { ior }
78    }
79}
80impl Normal {
81    /// Creates a new `Normal` material.
82    pub fn new() -> Self {
83        Normal {}
84    }
85}
86
87impl Material for Lambertian {
88    fn scatter(
89        &self,
90        _r_in: &Ray,
91        rec: &HitRecord,
92        attenuation: &mut Color,
93        scattered: &mut Ray,
94    ) -> bool {
95        let mut scatter_direction = rec.normal + (Vec3::random_normalized()); //on hit, send the ray in a random direction ( on the surface of the sphere )
96
97        //Checks to make sure the direction isnt too close to 0, which causes artfacting
98        if scatter_direction.near_zero() {
99            scatter_direction = rec.normal
100        }
101
102        *scattered = Ray::new(rec.p, scatter_direction); //send a new ray in the sactter direction
103                                                         //from from hitpoint (rec.p)
104        *attenuation = self.albedo;
105        true
106    }
107}
108
109impl Material for Normal {
110    fn scatter(
111        &self,
112        _r_in: &Ray,
113        rec: &HitRecord,
114        attenuation: &mut Color,
115        scattered: &mut Ray,
116    ) -> bool {
117        let scatter_direction = rec.normal + (Vec3::random_normalized());
118        *scattered = Ray::new(rec.p, scatter_direction);
119        *attenuation = Color::new(rec.normal.x, rec.normal.y, rec.normal.z);
120        false
121    }
122}
123
124impl Material for Metal {
125    fn scatter(
126        &self,
127        r_in: &Ray,
128        rec: &HitRecord,
129        attenuation: &mut Color,
130        scattered: &mut Ray,
131    ) -> bool {
132        let reflected: Vec3 = r_in.direction.reflect(&rec.normal);
133        let reflected = reflected.normalized() + Vec3::random_normalized() * self.fuzz;
134
135        *scattered = Ray::new(rec.p, reflected);
136        *attenuation = self.albedo;
137        return dot(&scattered.direction, &rec.normal) > 0.;
138    }
139}
140impl Material for Dielectric {
141    fn scatter(
142        &self,
143        r_in: &Ray,
144        rec: &HitRecord,
145        attenuation: &mut Color,
146        scattered: &mut Ray,
147    ) -> bool {
148        *attenuation = Color::new(1., 1., 1.);
149
150        let ri: f64 = if rec.front_face {
151            1.0 / self.ior
152        } else {
153            self.ior
154        };
155
156        let unit_direction = r_in.direction.normalized();
157        let cos_theta = f64::min(dot(&-unit_direction, &rec.normal), 1.0);
158        let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
159
160        let cannot_refract: bool = ri * sin_theta > 1.0;
161        let direction: Vec3;
162        if cannot_refract || reflectance(cos_theta, ri) > rand::thread_rng().gen_range(0.0..1.0) {
163            direction = unit_direction.reflect(&rec.normal)
164        } else {
165            direction = refract(unit_direction, &rec.normal, ri)
166        }
167
168        *scattered = Ray::new(rec.p, direction);
169
170        true
171    }
172}
173
174//schlick approximation for reflectance at grazing angles
175fn reflectance(cos: f64, ior: f64) -> f64 {
176    let r0 = (1. - ior) / (1. + ior);
177    let r0 = r0 * r0; //if everything breaks again try changing this
178    r0 + (1. - r0) * (1. - cos).powf(5.)
179}