world: add reflection to ray tracer.

This commit is contained in:
Bill Thiede 2021-07-25 16:28:34 -07:00
parent 0c7bbae4a3
commit 7f36aecf5e
4 changed files with 169 additions and 36 deletions

View File

@ -22,6 +22,8 @@ use crate::{
Float, BLACK, Float, BLACK,
}; };
const MAX_DEPTH_RECURSION: usize = 5;
#[derive(Copy, Clone, StructOpt, Debug, Deserialize)] #[derive(Copy, Clone, StructOpt, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum RenderStrategy { pub enum RenderStrategy {
@ -395,12 +397,12 @@ impl Camera {
let color = self let color = self
.supersample_rays_for_pixel(x, y, self.samples_per_pixel) .supersample_rays_for_pixel(x, y, self.samples_per_pixel)
.iter() .iter()
.map(|ray| w.color_at(&ray)) .map(|ray| w.color_at(&ray, MAX_DEPTH_RECURSION))
.fold(BLACK, |acc, c| acc + c); .fold(BLACK, |acc, c| acc + c);
color / self.samples_per_pixel as Float color / self.samples_per_pixel as Float
} else { } else {
let ray = self.ray_for_pixel(x, y); let ray = self.ray_for_pixel(x, y);
w.color_at(&ray) w.color_at(&ray, MAX_DEPTH_RECURSION)
} }
} }
} }

View File

@ -3,7 +3,7 @@ use std::ops::Index;
use crate::{ use crate::{
rays::Ray, rays::Ray,
shapes::Shape, shapes::Shape,
tuples::{dot, Tuple}, tuples::{dot, reflect, Tuple},
Float, EPSILON, Float, EPSILON,
}; };
@ -148,6 +148,7 @@ pub struct PrecomputedData<'i> {
pub over_point: Tuple, pub over_point: Tuple,
pub eyev: Tuple, pub eyev: Tuple,
pub normalv: Tuple, pub normalv: Tuple,
pub reflectv: Tuple,
pub inside: bool, pub inside: bool,
} }
@ -160,8 +161,8 @@ pub struct PrecomputedData<'i> {
/// rays::Ray, /// rays::Ray,
/// matrices::Matrix4x4, /// matrices::Matrix4x4,
/// shapes::{intersect, Shape}, /// shapes::{intersect, Shape},
/// tuples::Tuple, /// tuples::{point,vector,Tuple},
/// EPSILON /// EPSILON,Float
/// }; /// };
/// ///
/// // Precomputing the state of an intersection. /// // Precomputing the state of an intersection.
@ -201,6 +202,13 @@ pub struct PrecomputedData<'i> {
/// let comps = prepare_computations(&i, &r); /// let comps = prepare_computations(&i, &r);
/// assert!(comps.over_point.z< -EPSILON/2.); /// assert!(comps.over_point.z< -EPSILON/2.);
/// assert!(comps.point.z>comps.over_point.z); /// assert!(comps.point.z>comps.over_point.z);
///
/// // Precomputing the reflection vector.
/// let shape = Shape::plane();
/// let r= Ray::new(point(0.,1.,-1.), vector(0.,-(2 as Float).sqrt()/2., (2 as Float).sqrt()/2.));
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// assert_eq!(comps.reflectv, vector(0.,(2 as Float).sqrt()/2., (2 as Float).sqrt()/2.));
/// ``` /// ```
pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData<'i> { pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData<'i> {
let point = r.position(i.t); let point = r.position(i.t);
@ -211,6 +219,7 @@ pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData
} else { } else {
(false, normalv) (false, normalv)
}; };
let reflectv = reflect(r.direction, normalv);
let over_point = point + normalv * EPSILON; let over_point = point + normalv * EPSILON;
PrecomputedData { PrecomputedData {
@ -219,6 +228,7 @@ pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData
point, point,
over_point, over_point,
normalv, normalv,
reflectv,
inside, inside,
eyev, eyev,
} }

View File

@ -17,6 +17,7 @@ pub struct Material {
pub diffuse: Float, pub diffuse: Float,
pub specular: Float, pub specular: Float,
pub shininess: Float, pub shininess: Float,
pub reflective: Float,
} }
impl Default for Material { impl Default for Material {
@ -35,6 +36,7 @@ impl Default for Material {
/// diffuse: 0.9, /// diffuse: 0.9,
/// specular: 0.9, /// specular: 0.9,
/// shininess: 200., /// shininess: 200.,
/// reflective: 0.0,
/// } /// }
/// ); /// );
/// ``` /// ```
@ -45,6 +47,7 @@ impl Default for Material {
diffuse: 0.9, diffuse: 0.9,
specular: 0.9, specular: 0.9,
shininess: 200., shininess: 200.,
reflective: 0.0,
} }
} }
} }

View File

@ -93,21 +93,24 @@ impl World {
/// use rtchallenge::{ /// use rtchallenge::{
/// intersections::{prepare_computations, Intersection}, /// intersections::{prepare_computations, Intersection},
/// lights::PointLight, /// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::Matrix4x4, /// matrices::Matrix4x4,
/// rays::Ray, /// rays::Ray,
/// shapes::Shape, /// shapes::{Shape, ShapeBuilder},
/// tuples::{Color, Tuple}, /// tuples::{Color, Tuple},
/// world::World, /// world::World,
/// WHITE, /// Float, WHITE,
/// }; /// };
/// ///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // Shading an intersection. /// // Shading an intersection.
/// let w = World::test_world(); /// let w = World::test_world();
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); /// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
/// let s = &w.objects[0]; /// let s = &w.objects[0];
/// let i = Intersection::new(4., &s); /// let i = Intersection::new(4., &s);
/// let comps = prepare_computations(&i, &r); /// let comps = prepare_computations(&i, &r);
/// let c = w.shade_hit(&comps); /// let c = w.shade_hit(&comps, 1);
/// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855)); /// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855));
/// ///
/// // Shading an intersection from the inside. /// // Shading an intersection from the inside.
@ -117,7 +120,7 @@ impl World {
/// let s = &w.objects[1]; /// let s = &w.objects[1];
/// let i = Intersection::new(0.5, &s); /// let i = Intersection::new(0.5, &s);
/// let comps = prepare_computations(&i, &r); /// let comps = prepare_computations(&i, &r);
/// let c = w.shade_hit(&comps); /// let c = w.shade_hit(&comps, 1);
/// assert_eq!(c, Color::new(0.90498, 0.90498, 0.90498)); /// assert_eq!(c, Color::new(0.90498, 0.90498, 0.90498));
/// ///
/// // Shading with an intersection in shadow. /// // Shading with an intersection in shadow.
@ -130,16 +133,35 @@ impl World {
/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.)); /// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
/// let i = Intersection::new(4., &s2); /// let i = Intersection::new(4., &s2);
/// let comps = prepare_computations(&i, &r); /// let comps = prepare_computations(&i, &r);
/// let c = w.shade_hit(&comps); /// let c = w.shade_hit(&comps, 1);
/// assert_eq!(c, Color::new(0.1, 0.1, 0.1)); /// assert_eq!(c, Color::new(0.1, 0.1, 0.1));
///
/// // Shading with a reflective material.
/// let mut w = World::test_world();
/// let shape = ShapeBuilder::plane()
/// .material(MaterialBuilder::default().reflective(0.5).build()?)
/// .transform(Matrix4x4::translation(0., -1., 0.))
/// .build()?;
/// w.objects.push(shape.clone());
/// let r = Ray::new(
/// Tuple::point(0., 0., -3.),
/// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
/// );
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// let c = w.shade_hit(&comps, 1);
/// assert_eq!(c, [0.87676, 0.92434, 0.82917].into());
///
/// # Ok(())
/// # }
/// ``` /// ```
pub fn shade_hit(&self, comps: &PrecomputedData) -> Color { pub fn shade_hit(&self, comps: &PrecomputedData, remaining: usize) -> Color {
let c = self let surface = self
.lights .lights
.iter() .iter()
.fold(Color::new(0., 0., 0.), |acc, light| { .fold(Color::new(0., 0., 0.), |acc, light| {
let shadowed = self.is_shadowed(comps.over_point, light); let shadowed = self.is_shadowed(comps.over_point, light);
acc + lighting( let surface = lighting(
&comps.object.material, &comps.object.material,
&comps.object, &comps.object,
light, light,
@ -147,9 +169,11 @@ impl World {
comps.eyev, comps.eyev,
comps.normalv, comps.normalv,
shadowed, shadowed,
) );
acc + surface
}); });
c let reflected = self.reflected_color(comps, remaining);
surface + reflected
} }
/// Compute color for given ray fired at the world. /// Compute color for given ray fired at the world.
/// ///
@ -158,23 +182,27 @@ impl World {
/// use rtchallenge::{ /// use rtchallenge::{
/// intersections::{prepare_computations, Intersection}, /// intersections::{prepare_computations, Intersection},
/// lights::PointLight, /// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::translation,
/// rays::Ray, /// rays::Ray,
/// shapes::Shape, /// shapes::{Shape, ShapeBuilder},
/// tuples::{Color, Tuple}, /// tuples::{point, vector, Color, Tuple},
/// world::World, /// world::{World, WorldBuilder},
/// BLACK, WHITE, /// BLACK, WHITE,
/// }; /// };
/// ///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // The color when a ray misses. /// // The color when a ray misses.
/// let w = World::test_world(); /// let w = World::test_world();
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 1., 0.)); /// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 1., 0.));
/// let c = w.color_at(&r); /// let c = w.color_at(&r, 1);
/// assert_eq!(c, BLACK); /// assert_eq!(c, BLACK);
/// ///
/// // The color when a ray hits. /// // The color when a ray hits.
/// let w = World::test_world(); /// let w = World::test_world();
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); /// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
/// let c = w.color_at(&r); /// let c = w.color_at(&r, 1);
/// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855)); /// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855));
/// ///
/// // The color with an intersection behind the ray. /// // The color with an intersection behind the ray.
@ -188,15 +216,34 @@ impl World {
/// }; /// };
/// let inner = &w.objects[1]; /// let inner = &w.objects[1];
/// let r = Ray::new(Tuple::point(0., 0., 0.75), Tuple::vector(0., 0., -1.)); /// let r = Ray::new(Tuple::point(0., 0., 0.75), Tuple::vector(0., 0., -1.));
/// let c = w.color_at(&r); /// let c = w.color_at(&r, 1);
/// // inner.material.color is WHITE /// // inner.material.color is WHITE
/// assert_eq!(c, WHITE); /// assert_eq!(c, WHITE);
///
/// // Ensure mutually reflective surfaces don't infinitely recurse.
/// let lower = ShapeBuilder::plane()
/// .material(MaterialBuilder::default().reflective(1.).build()?)
/// .transform(translation(0., -1., 0.))
/// .build()?;
/// let upper = ShapeBuilder::plane()
/// .material(MaterialBuilder::default().reflective(1.).build()?)
/// .transform(translation(0., 1., 0.))
/// .build()?;
/// let w = WorldBuilder::default()
/// .lights(vec![PointLight::new(point(0., 0., 0.), WHITE)])
/// .objects(vec![lower, upper])
/// .build()?;
/// let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.));
/// // This should complete without stack overflow.
/// w.color_at(&r, 1);
/// # Ok(())
/// # }
/// ``` /// ```
pub fn color_at(&self, r: &Ray) -> Color { pub fn color_at(&self, r: &Ray, remaining: usize) -> Color {
match self.intersect(r).hit() { match self.intersect(r).hit() {
Some(hit) => { Some(hit) => {
let comps = prepare_computations(&hit, r); let comps = prepare_computations(&hit, r);
self.shade_hit(&comps) self.shade_hit(&comps, remaining)
} }
None => BLACK, None => BLACK,
} }
@ -206,11 +253,7 @@ impl World {
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// use rtchallenge::{ /// use rtchallenge::{shapes::Shape, tuples::Tuple, world::World};
/// tuples::{ Tuple},
/// shapes::Shape,
/// world::World,
/// };
/// ///
/// let w = World::test_world(); /// let w = World::test_world();
/// let light = &w.lights[0]; /// let light = &w.lights[0];
@ -230,6 +273,7 @@ impl World {
/// // There is no shadow when an object is behind the point. /// // There is no shadow when an object is behind the point.
/// let p = Tuple::point(-2., 2., -2.); /// let p = Tuple::point(-2., 2., -2.);
/// assert_eq!(w.is_shadowed(p, light), false); /// assert_eq!(w.is_shadowed(p, light), false);
/// ```
pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool { pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool {
let v = light.position - point; let v = light.position - point;
let distance = v.magnitude(); let distance = v.magnitude();
@ -242,4 +286,78 @@ impl World {
} }
false false
} }
/// Compute reflected color for the given precomputation.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// intersections::{prepare_computations, Intersection},
/// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::{translation, Matrix4x4},
/// rays::Ray,
/// shapes::{Shape, ShapeBuilder},
/// tuples::{point, vector, Color, Tuple},
/// world::World,
/// Float, BLACK,
/// };
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // The reflected color for a nonreflective material.
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
/// let mut w = World::test_world();
/// w.objects[1].material.ambient = 1.;
/// let i = Intersection::new(1., &w.objects[1]);
/// let comps = prepare_computations(&i, &r);
/// let c = w.reflected_color(&comps, 1);
/// assert_eq!(c, BLACK);
///
/// // The reflected color for a reflective material.
/// let mut w = World::test_world();
/// let shape = ShapeBuilder::plane()
/// .material(MaterialBuilder::default().reflective(0.5).build()?)
/// .transform(Matrix4x4::translation(0., -1., 0.))
/// .build()?;
/// w.objects.push(shape.clone());
/// let r = Ray::new(
/// Tuple::point(0., 0., -3.),
/// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
/// );
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// let c = w.reflected_color(&comps, 1);
/// assert_eq!(c, [0.19033, 0.23791, 0.14274].into());
///
/// // The reflected color at the maximum recursion depth.
/// let mut w = World::test_world();
/// let shape = ShapeBuilder::plane()
/// .material(MaterialBuilder::default().reflective(0.5).build()?)
/// .transform(translation(0., -1., 0.))
/// .build()?;
/// w.objects.push(shape.clone());
/// let r = Ray::new(
/// point(0., 0., -3.),
/// vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
/// );
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// let c = w.reflected_color(&comps, 0);
///
/// # Ok(())
/// # }
/// ```
pub fn reflected_color(&self, comps: &PrecomputedData, remaining: usize) -> Color {
if remaining == 0 {
return BLACK;
}
if comps.object.material.reflective == 0. {
return BLACK;
}
// Fire reflected ray to figure out color.
let reflect_ray = Ray::new(comps.over_point, comps.reflectv);
let color = self.color_at(&reflect_ray, remaining - 1);
color * comps.object.material.reflective
}
} }