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