Refactor, adding Material trait that returns a BSDF
This commit is contained in:
parent
9b8eea6f20
commit
cefbc2873b
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -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,
|
}),
|
||||||
},
|
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: _,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue