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}