From d8e547680674182f7d9e7ab4bd900134cce2f2af Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sat, 17 Jul 2021 09:31:23 -0700 Subject: [PATCH] materials: implement Phong lighting. --- rtchallenge/src/materials.rs | 95 +++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/rtchallenge/src/materials.rs b/rtchallenge/src/materials.rs index cbcbfb3..efb21d8 100644 --- a/rtchallenge/src/materials.rs +++ b/rtchallenge/src/materials.rs @@ -1,4 +1,8 @@ -use crate::tuples::Color; +use crate::{ + lights::PointLight, + tuples::Color, + tuples::{dot, reflect, Tuple}, +}; #[derive(Debug, PartialEq, Clone)] pub struct Material { pub color: Color, @@ -37,3 +41,92 @@ impl Default for Material { } } } + +const BLACK: Color = Color::new(0., 0., 0.); + +/// Compute lighting contributions using the Phong reflection model. +/// +/// # Examples +/// ``` +/// use rtchallenge::{ +/// lights::PointLight, +/// materials::{lighting, Material}, +/// tuples::{Color, Tuple}, +/// }; +/// +/// let m = Material::default(); +/// let position = Tuple::point(0., 0., 0.); +/// +/// // Lighting with the eye between the light and the surface. +/// let eyev = Tuple::vector(0., 0., -1.); +/// let normalv = Tuple::vector(0., 0., -1.); +/// let light = PointLight::new(Tuple::point(0., 0., -10.), Color::new(1., 1., 1.)); +/// let result = lighting(&m, &light, position, eyev, normalv); +/// assert_eq!(result, Color::new(1.9, 1.9, 1.9)); +/// +/// // Lighting with the eye between the light and the surface, eye offset 45°. +/// let eyev = Tuple::vector(0., 2_f32.sqrt() / 2., -2_f32.sqrt() / 2.); +/// let normalv = Tuple::vector(0., 0., -1.); +/// let light = PointLight::new(Tuple::point(0., 0., -10.), Color::new(1., 1., 1.)); +/// let result = lighting(&m, &light, position, eyev, normalv); +/// assert_eq!(result, Color::new(1.0, 1.0, 1.0)); +/// +/// // Lighting with the eye opposite surface, light offset 45°. +/// let eyev = Tuple::vector(0., 0., -1.); +/// let normalv = Tuple::vector(0., 0., -1.); +/// let light = PointLight::new(Tuple::point(0., 10., -10.), Color::new(1., 1., 1.)); +/// let result = lighting(&m, &light, position, eyev, normalv); +/// assert_eq!(result, Color::new(0.7364, 0.7364, 0.7364)); +/// +/// // Lighting with the eye in the path of the reflection vector. +/// let eyev = Tuple::vector(0., -2_f32.sqrt() / 2., -2_f32.sqrt() / 2.); +/// let normalv = Tuple::vector(0., 0., -1.); +/// let light = PointLight::new(Tuple::point(0., 10., -10.), Color::new(1., 1., 1.)); +/// let result = lighting(&m, &light, position, eyev, normalv); +/// assert_eq!(result, Color::new(1.6363853, 1.6363853, 1.6363853)); +/// +/// // Lighting with the light behind the surface. +/// let eyev = Tuple::vector(0., 0., -1.); +/// let normalv = Tuple::vector(0., 0., -1.); +/// let light = PointLight::new(Tuple::point(0., 0., 10.), Color::new(1., 1., 1.)); +/// let result = lighting(&m, &light, position, eyev, normalv); +/// assert_eq!(result, Color::new(0.1, 0.1, 0.1)); +/// ``` +pub fn lighting( + material: &Material, + light: &PointLight, + point: Tuple, + eyev: Tuple, + normalv: Tuple, +) -> Color { + // Combine the surface color with the light's color. + let effective_color = material.color * light.intensity; + // Find the direciton of the light source. + let lightv = (light.position - point).normalize(); + // Compute the ambient distribution. + let ambient = effective_color * material.ambient; + // This is the cosine of the angle between the light vector an the normal + // vector. A negative number means the light is on the other side of the + // surface. + let light_dot_normal = dot(lightv, normalv); + let (diffuse, specular) = if light_dot_normal < 0. { + (BLACK, BLACK) + } else { + // Compute the diffuse contribution. + let diffuse = effective_color * material.diffuse * light_dot_normal; + // This represents the cosine of the angle between the relfection vector + // and the eye vector. A negative number means the light reflects away + // from the eye. + let reflectv = reflect(-lightv, normalv); + let reflect_dot_eye = dot(reflectv, eyev); + let specular = if reflect_dot_eye < 0. { + BLACK + } else { + // Compute the specular contribution. + let factor = reflect_dot_eye.powf(material.shininess); + light.intensity * material.specular * factor + }; + (diffuse, specular) + }; + ambient + diffuse + specular +}