Refactor, adding Material trait that returns a BSDF

This commit is contained in:
Matthew Gordon 2019-11-23 20:53:45 -05:00
parent 9b8eea6f20
commit cefbc2873b
6 changed files with 105 additions and 48 deletions

View File

@ -61,18 +61,19 @@ impl<T: RealField> ImageSampler<T> {
} }
} }
pub fn render_scene<T: RealField>(output_image: &mut ImageRgbF<T>, scene: &Scene<T>) pub fn render_scene<'a, T: RealField>(output_image: &mut ImageRgbF<T>, scene: &Scene<T>) {
{
let image_sampler = ImageSampler::new( let image_sampler = ImageSampler::new(
output_image.get_width(), output_image.get_width(),
output_image.get_height(), output_image.get_height(),
scene.camera_location, scene.camera_location,
); );
let ambient_intensity: T = convert(0.0);
let directional_intensity: T = convert(0.9);
let integrator = PhongIntegrator::<T> { let integrator = PhongIntegrator::<T> {
ambient_light: convert(0.1), ambient_light: ColourRgbF::from_named(NamedColour::White) * ambient_intensity,
lights: vec![DirectionalLight { lights: vec![DirectionalLight {
direction: Vector3::new(convert(1.0), convert(1.0), convert(-1.0)).normalize(), direction: Vector3::new(convert(1.0), convert(1.0), convert(-1.0)).normalize(),
intensity: convert(0.3), colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity,
}], }],
}; };
for column in 0..output_image.get_width() { for column in 0..output_image.get_width() {
@ -100,8 +101,9 @@ pub fn render_scene<T: RealField>(output_image: &mut ImageRgbF<T>, scene: &Scene
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::materials::Material; use crate::materials::LambertianMaterial;
use crate::raycasting::{Intersect, IntersectionInfo, Plane}; use crate::raycasting::{Intersect, IntersectionInfo, Plane};
use std::rc::Rc;
#[cfg(test)] #[cfg(test)]
mod imagesampler { mod imagesampler {
@ -126,7 +128,7 @@ mod tests {
let film_plane = Plane::new( let film_plane = Plane::new(
Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.0, 0.0, 1.0),
target.film_distance, target.film_distance,
Material::<f64>::new_dummy(), Rc::new(LambertianMaterial::<f64>::new_dummy()),
); );
let point_on_film_plane = match film_plane.intersect(&ray) { let point_on_film_plane = match film_plane.intersect(&ray) {
Some(IntersectionInfo { Some(IntersectionInfo {

View File

@ -9,20 +9,22 @@ pub trait Integrator<T: RealField> {
pub struct DirectionalLight<T: RealField> { pub struct DirectionalLight<T: RealField> {
pub direction: Vector3<T>, pub direction: Vector3<T>,
pub intensity: T, pub colour: ColourRgbF<T>,
} }
pub struct PhongIntegrator<T: RealField> { pub struct PhongIntegrator<T: RealField> {
pub ambient_light: T, pub ambient_light: ColourRgbF<T>,
pub lights: Vec<DirectionalLight<T>>, pub lights: Vec<DirectionalLight<T>>,
} }
impl<T: RealField> Integrator<T> for PhongIntegrator<T> { impl<T: RealField> Integrator<T> for PhongIntegrator<T> {
fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRgbF<T> { fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRgbF<T> {
let intensity = self.lights self.lights
.iter() .iter()
.map(|light| light.intensity * light.direction.dot(&info.normal)) .map(|light| {
.fold(self.ambient_light, |a, b| a + b); info.material.bsdf()(info.retro, light.direction, light.colour)
ColourRgbF::from_vector3(&(info.material.colour.as_vector3() * intensity)) * light.direction.dot(&info.normal)
})
.fold(self.ambient_light, |a, b| a + b)
} }
} }

View File

@ -7,10 +7,12 @@ use std::time::Duration;
use nalgebra::Vector3; use nalgebra::Vector3;
use std::rc::Rc;
use vanrijn::camera::render_scene; use vanrijn::camera::render_scene;
use vanrijn::colour::{ColourRgbF, NamedColour}; use vanrijn::colour::{ColourRgbF, NamedColour};
use vanrijn::image::{ClampingToneMapper, ImageRgbF, ImageRgbU8, ToneMapper}; use vanrijn::image::{ClampingToneMapper, ImageRgbF, ImageRgbU8, ToneMapper};
use vanrijn::materials::Material; use vanrijn::materials::LambertianMaterial;
use vanrijn::raycasting::{Plane, Sphere}; use vanrijn::raycasting::{Plane, Sphere};
use vanrijn::scene::Scene; use vanrijn::scene::Scene;
@ -61,18 +63,16 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
Box::new(Plane::new( Box::new(Plane::new(
Vector3::new(0.0, 1.0, 0.0), Vector3::new(0.0, 1.0, 0.0),
-2.0, -2.0,
Material { Rc::new(LambertianMaterial {
colour: ColourRgbF::from_named(NamedColour::Green), colour: ColourRgbF::from_named(NamedColour::Green),
smoothness: 0.0, }),
},
)), )),
Box::new(Sphere::new( Box::new(Sphere::new(
Vector3::new(0.0, 1.0, 5.0), Vector3::new(0.0, 1.0, 5.0),
1.0, 1.0,
Material { Rc::new(LambertianMaterial {
colour: ColourRgbF::from_named(NamedColour::Blue), colour: ColourRgbF::from_named(NamedColour::Blue),
smoothness: 0.7, }),
},
)), )),
], ],
}; };

View File

@ -1,18 +1,36 @@
use nalgebra::RealField; use nalgebra::{RealField, Vector3};
use super::colour::ColourRgbF; use super::colour::ColourRgbF;
#[derive(Debug)] use std::fmt::Debug;
pub struct Material<T: RealField> {
pub colour: ColourRgbF<T>, pub trait Material<T: RealField>: Debug {
pub smoothness: T, fn bsdf<'a>(
&'a self,
) -> Box<dyn Fn(Vector3<T>, Vector3<T>, ColourRgbF<T>) -> ColourRgbF<T> + 'a>;
} }
impl<T: RealField> Material<T> { #[derive(Debug)]
pub fn new_dummy() -> Material<T> { pub struct LambertianMaterial<T: RealField> {
Material { pub colour: ColourRgbF<T>,
}
impl<T: RealField> LambertianMaterial<T> {
pub fn new_dummy() -> LambertianMaterial<T> {
LambertianMaterial {
colour: ColourRgbF::new(T::one(), T::one(), T::one()), colour: ColourRgbF::new(T::one(), T::one(), T::one()),
smoothness: T::zero(),
} }
} }
} }
impl<T: RealField> Material<T> for LambertianMaterial<T> {
fn bsdf<'a>(
&'a self,
) -> Box<dyn Fn(Vector3<T>, Vector3<T>, ColourRgbF<T>) -> ColourRgbF<T> + 'a> {
Box::new(
move |_w_o: Vector3<T>, _w_i: Vector3<T>, colour_in: ColourRgbF<T>| {
self.colour * colour_in
},
)
}
}

View File

@ -2,6 +2,8 @@ use nalgebra::{convert, RealField, Vector3};
use super::materials::Material; use super::materials::Material;
use std::rc::Rc;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Ray<T: RealField> { pub struct Ray<T: RealField> {
origin: Vector3<T>, origin: Vector3<T>,
@ -22,26 +24,26 @@ impl<T: RealField> Ray<T> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct IntersectionInfo<'a, T: RealField> { pub struct IntersectionInfo<T: RealField> {
pub distance: T, pub distance: T,
pub location: Vector3<T>, pub location: Vector3<T>,
pub normal: Vector3<T>, pub normal: Vector3<T>,
pub retro: Vector3<T>, pub retro: Vector3<T>,
pub material: &'a Material<T>, pub material: Rc<dyn Material<T>>,
} }
pub trait Intersect<T: RealField> { pub trait Intersect<T: RealField> {
fn intersect(&self, ray: &Ray<T>) -> Option<IntersectionInfo<T>>; fn intersect<'a>(&'a self, ray: &Ray<T>) -> Option<IntersectionInfo<T>>;
} }
pub struct Sphere<T: RealField> { pub struct Sphere<T: RealField> {
centre: Vector3<T>, centre: Vector3<T>,
radius: T, radius: T,
material: Material<T>, material: Rc<dyn Material<T>>,
} }
impl<T: RealField> Sphere<T> { impl<T: RealField> Sphere<T> {
pub fn new(centre: Vector3<T>, radius: T, material: Material<T>) -> Sphere<T> { pub fn new(centre: Vector3<T>, radius: T, material: Rc<dyn Material<T>>) -> Sphere<T> {
Sphere { Sphere {
centre, centre,
radius, radius,
@ -51,7 +53,7 @@ impl<T: RealField> Sphere<T> {
} }
impl<T: RealField> Intersect<T> for Sphere<T> { impl<T: RealField> Intersect<T> for Sphere<T> {
fn intersect(&self, ray: &Ray<T>) -> Option<IntersectionInfo<T>> { fn intersect<'a>(&'a self, ray: &Ray<T>) -> Option<IntersectionInfo<T>> {
let ray_origin_to_sphere_centre = self.centre - ray.origin; let ray_origin_to_sphere_centre = self.centre - ray.origin;
let radius_squared = self.radius * self.radius; let radius_squared = self.radius * self.radius;
let is_inside_sphere = ray_origin_to_sphere_centre.norm_squared() <= radius_squared; let is_inside_sphere = ray_origin_to_sphere_centre.norm_squared() <= radius_squared;
@ -84,7 +86,7 @@ impl<T: RealField> Intersect<T> for Sphere<T> {
location, location,
normal, normal,
retro, retro,
material: &self.material, material: Rc::clone(&self.material),
}) })
} }
} }
@ -92,11 +94,15 @@ impl<T: RealField> Intersect<T> for Sphere<T> {
pub struct Plane<T: RealField> { pub struct Plane<T: RealField> {
normal: Vector3<T>, normal: Vector3<T>,
distance_from_origin: T, distance_from_origin: T,
material: Material<T>, material: Rc<dyn Material<T>>,
} }
impl<T: RealField> Plane<T> { impl<T: RealField> Plane<T> {
pub fn new(normal: Vector3<T>, distance_from_origin: T, material: Material<T>) -> Plane<T> { pub fn new(
normal: Vector3<T>,
distance_from_origin: T,
material: Rc<dyn Material<T>>,
) -> Plane<T> {
normal.normalize(); normal.normalize();
Plane { Plane {
normal, normal,
@ -107,7 +113,7 @@ impl<T: RealField> Plane<T> {
} }
impl<T: RealField> Intersect<T> for Plane<T> { impl<T: RealField> Intersect<T> for Plane<T> {
fn intersect(&self, ray: &Ray<T>) -> Option<IntersectionInfo<T>> { fn intersect<'a>(&'a self, ray: &Ray<T>) -> Option<IntersectionInfo<T>> {
let ray_direction_dot_plane_normal = ray.direction.dot(&self.normal); let ray_direction_dot_plane_normal = ray.direction.dot(&self.normal);
let point_on_plane = self.normal * self.distance_from_origin; let point_on_plane = self.normal * self.distance_from_origin;
let point_on_plane_minus_ray_origin_dot_normal = let point_on_plane_minus_ray_origin_dot_normal =
@ -128,7 +134,7 @@ impl<T: RealField> Intersect<T> for Plane<T> {
location: ray.point_at(t), location: ray.point_at(t),
normal: self.normal, normal: self.normal,
retro: -ray.direction, retro: -ray.direction,
material: &self.material, material: Rc::clone(&self.material),
}) })
} }
} }
@ -148,6 +154,7 @@ mod tests {
} }
use super::*; use super::*;
use crate::materials::LambertianMaterial;
use quickcheck::{Arbitrary, Gen}; use quickcheck::{Arbitrary, Gen};
impl<T: Arbitrary + RealField> Arbitrary for Ray<T> { impl<T: Arbitrary + RealField> Arbitrary for Ray<T> {
fn arbitrary<G: Gen>(g: &mut G) -> Ray<T> { fn arbitrary<G: Gen>(g: &mut G) -> Ray<T> {
@ -188,49 +195,77 @@ mod tests {
#[test] #[test]
fn ray_intersects_sphere() { fn ray_intersects_sphere() {
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0)); let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
let s = Sphere::new(Vector3::new(1.5, 1.5, 15.0), 5.0, Material::new_dummy()); let s = Sphere::new(
Vector3::new(1.5, 1.5, 15.0),
5.0,
Rc::new(LambertianMaterial::new_dummy()),
);
assert_matches!(s.intersect(&r), Some(_)); assert_matches!(s.intersect(&r), Some(_));
} }
#[test] #[test]
fn ray_does_not_intersect_sphere_when_sphere_is_in_front() { fn ray_does_not_intersect_sphere_when_sphere_is_in_front() {
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0)); let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
let s = Sphere::new(Vector3::new(-5.0, 1.5, 15.0), 5.0, Material::new_dummy()); let s = Sphere::new(
Vector3::new(-5.0, 1.5, 15.0),
5.0,
Rc::new(LambertianMaterial::new_dummy()),
);
assert_matches!(s.intersect(&r), None); assert_matches!(s.intersect(&r), None);
} }
#[test] #[test]
fn ray_does_not_intersect_sphere_when_sphere_is_behind() { fn ray_does_not_intersect_sphere_when_sphere_is_behind() {
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0)); let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
let s = Sphere::new(Vector3::new(1.5, 1.5, -15.0), 5.0, Material::new_dummy()); let s = Sphere::new(
Vector3::new(1.5, 1.5, -15.0),
5.0,
Rc::new(LambertianMaterial::new_dummy()),
);
assert_matches!(s.intersect(&r), None); assert_matches!(s.intersect(&r), None);
} }
#[test] #[test]
fn ray_intersects_sphere_when_origin_is_inside() { fn ray_intersects_sphere_when_origin_is_inside() {
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0)); let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
let s = Sphere::new(Vector3::new(1.5, 1.5, 2.0), 5.0, Material::new_dummy()); let s = Sphere::new(
Vector3::new(1.5, 1.5, 2.0),
5.0,
Rc::new(LambertianMaterial::new_dummy()),
);
assert_matches!(s.intersect(&r), Some(_)); assert_matches!(s.intersect(&r), Some(_));
} }
#[test] #[test]
fn ray_intersects_plane() { fn ray_intersects_plane() {
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(-1.0, 0.0, 1.0)); let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(-1.0, 0.0, 1.0));
let p = Plane::new(Vector3::new(1.0, 0.0, 0.0), -5.0, Material::new_dummy()); let p = Plane::new(
Vector3::new(1.0, 0.0, 0.0),
-5.0,
Rc::new(LambertianMaterial::new_dummy()),
);
assert_matches!(p.intersect(&r), Some(_)); assert_matches!(p.intersect(&r), Some(_));
} }
#[test] #[test]
fn ray_does_not_intersect_plane() { fn ray_does_not_intersect_plane() {
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(1.0, 0.0, 1.0)); let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(1.0, 0.0, 1.0));
let p = Plane::new(Vector3::new(1.0, 0.0, 0.0), -5.0, Material::new_dummy()); let p = Plane::new(
Vector3::new(1.0, 0.0, 0.0),
-5.0,
Rc::new(LambertianMaterial::new_dummy()),
);
assert_matches!(p.intersect(&r), None); assert_matches!(p.intersect(&r), None);
} }
#[test] #[test]
fn intersection_point_is_on_plane() { fn intersection_point_is_on_plane() {
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(-1.0, 0.0, 1.0)); let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(-1.0, 0.0, 1.0));
let p = Plane::new(Vector3::new(1.0, 0.0, 0.0), -5.0, Material::new_dummy()); let p = Plane::new(
Vector3::new(1.0, 0.0, 0.0),
-5.0,
Rc::new(LambertianMaterial::new_dummy()),
);
match p.intersect(&r) { match p.intersect(&r) {
Some(IntersectionInfo { Some(IntersectionInfo {
distance: _, distance: _,