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,
};
const MAX_DEPTH_RECURSION: usize = 5;
#[derive(Copy, Clone, StructOpt, Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum RenderStrategy {
@ -395,12 +397,12 @@ impl Camera {
let color = self
.supersample_rays_for_pixel(x, y, self.samples_per_pixel)
.iter()
.map(|ray| w.color_at(&ray))
.map(|ray| w.color_at(&ray, MAX_DEPTH_RECURSION))
.fold(BLACK, |acc, c| acc + c);
color / self.samples_per_pixel as Float
} else {
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::{
rays::Ray,
shapes::Shape,
tuples::{dot, Tuple},
tuples::{dot, reflect, Tuple},
Float, EPSILON,
};
@ -148,6 +148,7 @@ pub struct PrecomputedData<'i> {
pub over_point: Tuple,
pub eyev: Tuple,
pub normalv: Tuple,
pub reflectv: Tuple,
pub inside: bool,
}
@ -160,8 +161,8 @@ pub struct PrecomputedData<'i> {
/// rays::Ray,
/// matrices::Matrix4x4,
/// shapes::{intersect, Shape},
/// tuples::Tuple,
/// EPSILON
/// tuples::{point,vector,Tuple},
/// EPSILON,Float
/// };
///
/// // Precomputing the state of an intersection.
@ -201,6 +202,13 @@ pub struct PrecomputedData<'i> {
/// let comps = prepare_computations(&i, &r);
/// assert!(comps.over_point.z< -EPSILON/2.);
/// 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> {
let point = r.position(i.t);
@ -211,6 +219,7 @@ pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData
} else {
(false, normalv)
};
let reflectv = reflect(r.direction, normalv);
let over_point = point + normalv * EPSILON;
PrecomputedData {
@ -219,6 +228,7 @@ pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData
point,
over_point,
normalv,
reflectv,
inside,
eyev,
}

View File

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

View File

@ -93,21 +93,24 @@ impl World {
/// use rtchallenge::{
/// intersections::{prepare_computations, Intersection},
/// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::Matrix4x4,
/// rays::Ray,
/// shapes::Shape,
/// shapes::{Shape, ShapeBuilder},
/// tuples::{Color, Tuple},
/// world::World,
/// WHITE,
/// Float, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // Shading an intersection.
/// let w = World::test_world();
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
/// let s = &w.objects[0];
/// let i = Intersection::new(4., &s);
/// 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));
///
/// // Shading an intersection from the inside.
@ -117,7 +120,7 @@ impl World {
/// let s = &w.objects[1];
/// let i = Intersection::new(0.5, &s);
/// 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));
///
/// // 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 i = Intersection::new(4., &s2);
/// 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));
///
/// // 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 {
let c = self
pub fn shade_hit(&self, comps: &PrecomputedData, remaining: usize) -> Color {
let surface = self
.lights
.iter()
.fold(Color::new(0., 0., 0.), |acc, light| {
let shadowed = self.is_shadowed(comps.over_point, light);
acc + lighting(
let surface = lighting(
&comps.object.material,
&comps.object,
light,
@ -147,9 +169,11 @@ impl World {
comps.eyev,
comps.normalv,
shadowed,
)
);
acc + surface
});
c
let reflected = self.reflected_color(comps, remaining);
surface + reflected
}
/// Compute color for given ray fired at the world.
///
@ -158,23 +182,27 @@ impl World {
/// use rtchallenge::{
/// intersections::{prepare_computations, Intersection},
/// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::translation,
/// rays::Ray,
/// shapes::Shape,
/// tuples::{Color, Tuple},
/// world::World,
/// shapes::{Shape, ShapeBuilder},
/// tuples::{point, vector, Color, Tuple},
/// world::{World, WorldBuilder},
/// BLACK, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // The color when a ray misses.
/// let w = World::test_world();
/// 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);
///
/// // The color when a ray hits.
/// let w = World::test_world();
/// 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));
///
/// // The color with an intersection behind the ray.
@ -188,15 +216,34 @@ impl World {
/// };
/// let inner = &w.objects[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
/// 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() {
Some(hit) => {
let comps = prepare_computations(&hit, r);
self.shade_hit(&comps)
self.shade_hit(&comps, remaining)
}
None => BLACK,
}
@ -206,30 +253,27 @@ impl World {
///
/// # Examples
/// ```
/// use rtchallenge::{
/// tuples::{ Tuple},
/// shapes::Shape,
/// world::World,
/// };
/// use rtchallenge::{shapes::Shape, tuples::Tuple, world::World};
///
/// let w = World::test_world();
/// let light = &w.lights[0];
///
/// // There is no shadow when nothing is collinear with point and light.
/// let p = Tuple::point(0.,10.,0.);
/// assert_eq!(w.is_shadowed(p,light), false);
/// let p = Tuple::point(0., 10., 0.);
/// assert_eq!(w.is_shadowed(p, light), false);
///
/// // There shadow when an object is between the point and the light.
/// let p = Tuple::point(10.,-10.,10.);
/// assert_eq!(w.is_shadowed(p,light), true);
/// let p = Tuple::point(10., -10., 10.);
/// assert_eq!(w.is_shadowed(p, light), true);
///
/// // There is no shadow when an object is behind the light.
/// let p = Tuple::point(-20.,20.,-20.);
/// assert_eq!(w.is_shadowed(p,light), false);
/// let p = Tuple::point(-20., 20., -20.);
/// assert_eq!(w.is_shadowed(p, light), false);
///
/// // There is no shadow when an object is behind the point.
/// let p = Tuple::point(-2.,2.,-2.);
/// assert_eq!(w.is_shadowed(p,light), false);
/// let p = Tuple::point(-2., 2., -2.);
/// assert_eq!(w.is_shadowed(p, light), false);
/// ```
pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool {
let v = light.position - point;
let distance = v.magnitude();
@ -242,4 +286,78 @@ impl World {
}
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
}
}