world: add reflection to ray tracer.
This commit is contained in:
parent
0c7bbae4a3
commit
7f36aecf5e
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user