diff --git a/src/camera.rs b/src/camera.rs index cf606bc..a3188d8 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -61,18 +61,19 @@ impl ImageSampler { } } -pub fn render_scene(output_image: &mut ImageRgbF, scene: &Scene) -{ +pub fn render_scene<'a, T: RealField>(output_image: &mut ImageRgbF, scene: &Scene) { let image_sampler = ImageSampler::new( output_image.get_width(), output_image.get_height(), scene.camera_location, ); + let ambient_intensity: T = convert(0.0); + let directional_intensity: T = convert(0.9); let integrator = PhongIntegrator:: { - ambient_light: convert(0.1), + ambient_light: ColourRgbF::from_named(NamedColour::White) * ambient_intensity, lights: vec![DirectionalLight { 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() { @@ -100,8 +101,9 @@ pub fn render_scene(output_image: &mut ImageRgbF, scene: &Scene #[cfg(test)] mod tests { use super::*; - use crate::materials::Material; + use crate::materials::LambertianMaterial; use crate::raycasting::{Intersect, IntersectionInfo, Plane}; + use std::rc::Rc; #[cfg(test)] mod imagesampler { @@ -126,7 +128,7 @@ mod tests { let film_plane = Plane::new( Vector3::new(0.0, 0.0, 1.0), target.film_distance, - Material::::new_dummy(), + Rc::new(LambertianMaterial::::new_dummy()), ); let point_on_film_plane = match film_plane.intersect(&ray) { Some(IntersectionInfo { diff --git a/src/integrators.rs b/src/integrators.rs index db81330..5f7ceb8 100644 --- a/src/integrators.rs +++ b/src/integrators.rs @@ -9,20 +9,22 @@ pub trait Integrator { pub struct DirectionalLight { pub direction: Vector3, - pub intensity: T, + pub colour: ColourRgbF, } pub struct PhongIntegrator { - pub ambient_light: T, + pub ambient_light: ColourRgbF, pub lights: Vec>, } impl Integrator for PhongIntegrator { fn integrate(&self, info: &IntersectionInfo) -> ColourRgbF { - let intensity = self.lights + self.lights .iter() - .map(|light| light.intensity * light.direction.dot(&info.normal)) - .fold(self.ambient_light, |a, b| a + b); - ColourRgbF::from_vector3(&(info.material.colour.as_vector3() * intensity)) + .map(|light| { + info.material.bsdf()(info.retro, light.direction, light.colour) + * light.direction.dot(&info.normal) + }) + .fold(self.ambient_light, |a, b| a + b) } } diff --git a/src/main.rs b/src/main.rs index ebf8b6c..5162c76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,12 @@ use std::time::Duration; use nalgebra::Vector3; +use std::rc::Rc; + use vanrijn::camera::render_scene; use vanrijn::colour::{ColourRgbF, NamedColour}; use vanrijn::image::{ClampingToneMapper, ImageRgbF, ImageRgbU8, ToneMapper}; -use vanrijn::materials::Material; +use vanrijn::materials::LambertianMaterial; use vanrijn::raycasting::{Plane, Sphere}; use vanrijn::scene::Scene; @@ -61,18 +63,16 @@ pub fn main() -> Result<(), Box> { Box::new(Plane::new( Vector3::new(0.0, 1.0, 0.0), -2.0, - Material { + Rc::new(LambertianMaterial { colour: ColourRgbF::from_named(NamedColour::Green), - smoothness: 0.0, - }, + }), )), Box::new(Sphere::new( Vector3::new(0.0, 1.0, 5.0), 1.0, - Material { + Rc::new(LambertianMaterial { colour: ColourRgbF::from_named(NamedColour::Blue), - smoothness: 0.7, - }, + }), )), ], }; diff --git a/src/materials.rs b/src/materials.rs index edc4194..a20391a 100644 --- a/src/materials.rs +++ b/src/materials.rs @@ -1,18 +1,36 @@ -use nalgebra::RealField; +use nalgebra::{RealField, Vector3}; use super::colour::ColourRgbF; -#[derive(Debug)] -pub struct Material { - pub colour: ColourRgbF, - pub smoothness: T, +use std::fmt::Debug; + +pub trait Material: Debug { + fn bsdf<'a>( + &'a self, + ) -> Box, Vector3, ColourRgbF) -> ColourRgbF + 'a>; } -impl Material { - pub fn new_dummy() -> Material { - Material { +#[derive(Debug)] +pub struct LambertianMaterial { + pub colour: ColourRgbF, +} + +impl LambertianMaterial { + pub fn new_dummy() -> LambertianMaterial { + LambertianMaterial { colour: ColourRgbF::new(T::one(), T::one(), T::one()), - smoothness: T::zero(), } } } + +impl Material for LambertianMaterial { + fn bsdf<'a>( + &'a self, + ) -> Box, Vector3, ColourRgbF) -> ColourRgbF + 'a> { + Box::new( + move |_w_o: Vector3, _w_i: Vector3, colour_in: ColourRgbF| { + self.colour * colour_in + }, + ) + } +} diff --git a/src/raycasting.rs b/src/raycasting.rs index 59a80ba..861155f 100644 --- a/src/raycasting.rs +++ b/src/raycasting.rs @@ -2,6 +2,8 @@ use nalgebra::{convert, RealField, Vector3}; use super::materials::Material; +use std::rc::Rc; + #[derive(Clone, Debug)] pub struct Ray { origin: Vector3, @@ -22,26 +24,26 @@ impl Ray { } #[derive(Debug)] -pub struct IntersectionInfo<'a, T: RealField> { +pub struct IntersectionInfo { pub distance: T, pub location: Vector3, pub normal: Vector3, pub retro: Vector3, - pub material: &'a Material, + pub material: Rc>, } pub trait Intersect { - fn intersect(&self, ray: &Ray) -> Option>; + fn intersect<'a>(&'a self, ray: &Ray) -> Option>; } pub struct Sphere { centre: Vector3, radius: T, - material: Material, + material: Rc>, } impl Sphere { - pub fn new(centre: Vector3, radius: T, material: Material) -> Sphere { + pub fn new(centre: Vector3, radius: T, material: Rc>) -> Sphere { Sphere { centre, radius, @@ -51,7 +53,7 @@ impl Sphere { } impl Intersect for Sphere { - fn intersect(&self, ray: &Ray) -> Option> { + fn intersect<'a>(&'a self, ray: &Ray) -> Option> { let ray_origin_to_sphere_centre = self.centre - ray.origin; let radius_squared = self.radius * self.radius; let is_inside_sphere = ray_origin_to_sphere_centre.norm_squared() <= radius_squared; @@ -84,7 +86,7 @@ impl Intersect for Sphere { location, normal, retro, - material: &self.material, + material: Rc::clone(&self.material), }) } } @@ -92,11 +94,15 @@ impl Intersect for Sphere { pub struct Plane { normal: Vector3, distance_from_origin: T, - material: Material, + material: Rc>, } impl Plane { - pub fn new(normal: Vector3, distance_from_origin: T, material: Material) -> Plane { + pub fn new( + normal: Vector3, + distance_from_origin: T, + material: Rc>, + ) -> Plane { normal.normalize(); Plane { normal, @@ -107,7 +113,7 @@ impl Plane { } impl Intersect for Plane { - fn intersect(&self, ray: &Ray) -> Option> { + fn intersect<'a>(&'a self, ray: &Ray) -> Option> { 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_minus_ray_origin_dot_normal = @@ -128,7 +134,7 @@ impl Intersect for Plane { location: ray.point_at(t), normal: self.normal, retro: -ray.direction, - material: &self.material, + material: Rc::clone(&self.material), }) } } @@ -148,6 +154,7 @@ mod tests { } use super::*; + use crate::materials::LambertianMaterial; use quickcheck::{Arbitrary, Gen}; impl Arbitrary for Ray { fn arbitrary(g: &mut G) -> Ray { @@ -188,49 +195,77 @@ mod tests { #[test] 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 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(_)); } #[test] 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 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); } #[test] 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 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); } #[test] 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 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(_)); } #[test] 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 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(_)); } #[test] 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 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); } #[test] 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 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) { Some(IntersectionInfo { distance: _, diff --git a/src/scene.rs b/src/scene.rs index 7dc67d3..16ee56f 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,8 +1,8 @@ -use nalgebra::{RealField,Vector3}; +use nalgebra::{RealField, Vector3}; use crate::raycasting::Intersect; -pub struct Scene { +pub struct Scene { pub camera_location: Vector3, pub objects: Vec>>, }