Shapes can now be colours other than gray

This commit is contained in:
Matthew Gordon 2019-11-19 07:43:00 -05:00
parent f193fbf84b
commit f13b585bfe
7 changed files with 114 additions and 55 deletions

View File

@ -1,5 +1,6 @@
use nalgebra::{clamp, convert, RealField, Vector3}; use nalgebra::{convert, RealField, Vector3};
use super::colour::{ClampingToneMapper, NamedColour, NormalizedAsByte, ToneMapper};
use super::image::OutputImage; use super::image::OutputImage;
use super::integrators::{DirectionalLight, Integrator, PhongIntegrator}; use super::integrators::{DirectionalLight, Integrator, PhongIntegrator};
use super::raycasting::Ray; use super::raycasting::Ray;
@ -60,8 +61,10 @@ impl<T: RealField> ImageSampler<T> {
} }
} }
pub fn render_scene<T: RealField>(output_image: &mut OutputImage, scene: &Scene<T>) pub fn render_scene<T: RealField + NormalizedAsByte>(
where output_image: &mut OutputImage,
scene: &Scene<T>,
) where
f32: From<T>, f32: From<T>,
{ {
let image_sampler = ImageSampler::new( let image_sampler = ImageSampler::new(
@ -76,6 +79,7 @@ where
intensity: convert(0.3), intensity: convert(0.3),
}], }],
}; };
let tone_mapper = ClampingToneMapper {};
for column in 0..output_image.get_width() { for column in 0..output_image.get_width() {
for row in 0..output_image.get_height() { for row in 0..output_image.get_height() {
let ray = image_sampler.ray_for_pixel(row, column); let ray = image_sampler.ray_for_pixel(row, column);
@ -89,12 +93,12 @@ where
Some(ordering) => ordering, Some(ordering) => ordering,
}, },
); );
let gray = match hit { let colour = match hit {
None => convert(0.0), None => NamedColour::Black.as_colourrgb(),
Some(intersection_info) => integrator.integrate(&intersection_info), Some(intersection_info) => integrator.integrate(&intersection_info),
}; };
let gray = f32::from(clamp(gray * convert(255.0), convert(0.0), convert(255.0))) as u8; let colour = tone_mapper.apply_tone_mapping(&colour);
output_image.set_color(row, column, gray, gray, gray); output_image.set_color(row, column, colour);
} }
} }
} }
@ -102,7 +106,9 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::materials::Material;
use crate::raycasting::{Intersect, IntersectionInfo, Plane}; use crate::raycasting::{Intersect, IntersectionInfo, Plane};
#[cfg(test)] #[cfg(test)]
mod imagesampler { mod imagesampler {
use super::*; use super::*;
@ -123,13 +129,18 @@ mod tests {
fn ray_for_pixel_returns_value_that_intersects_film_plane_at_expected_location() { fn ray_for_pixel_returns_value_that_intersects_film_plane_at_expected_location() {
let target = ImageSampler::new(800, 600, Vector3::new(0.0, 0.0, 0.0)); let target = ImageSampler::new(800, 600, Vector3::new(0.0, 0.0, 0.0));
let ray = target.ray_for_pixel(100, 200); let ray = target.ray_for_pixel(100, 200);
let film_plane = Plane::new(Vector3::new(0.0, 0.0, 1.0), target.film_distance); let film_plane = Plane::new(
Vector3::new(0.0, 0.0, 1.0),
target.film_distance,
Material::<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 {
location, location,
distance: _, distance: _,
normal: _, normal: _,
retro: _, retro: _,
material: _,
}) => location, }) => location,
None => panic!(), None => panic!(),
}; };

View File

@ -1,5 +1,6 @@
use nalgebra::{clamp, convert, RealField, Vector3}; use nalgebra::{clamp, convert, RealField, Vector3};
#[derive(Debug)]
pub struct ColourRGB<T: RealField> { pub struct ColourRGB<T: RealField> {
values: Vector3<T>, values: Vector3<T>,
} }
@ -16,6 +17,10 @@ impl<T: RealField + NormalizedAsByte> ColourRGB<T> {
} }
} }
pub fn from_vector3(v: &Vector3<T>) -> ColourRGB<T> {
ColourRGB { values: *v }
}
pub fn red(&self) -> T { pub fn red(&self) -> T {
self.values[0] self.values[0]
} }
@ -83,11 +88,12 @@ pub enum NamedColour {
Navy, Navy,
} }
pub fn named_colour<T: RealField+NormalizedAsByte>(colour: NamedColour) -> ColourRGB<T> { impl NamedColour {
pub fn as_colourrgb<T: RealField + NormalizedAsByte>(self) -> ColourRGB<T> {
let zero: T = convert(0.0); let zero: T = convert(0.0);
let half: T = convert(0.5); let half: T = convert(0.5);
let one: T = convert(1.0); let one: T = convert(1.0);
match colour { match self {
NamedColour::Black => ColourRGB::new(zero, zero, zero), NamedColour::Black => ColourRGB::new(zero, zero, zero),
NamedColour::White => ColourRGB::new(one, one, one), NamedColour::White => ColourRGB::new(one, one, one),
NamedColour::Red => ColourRGB::new(one, zero, zero), NamedColour::Red => ColourRGB::new(one, zero, zero),
@ -99,12 +105,13 @@ pub fn named_colour<T: RealField+NormalizedAsByte>(colour: NamedColour) -> Colou
NamedColour::Gray => ColourRGB::new(half, half, half), NamedColour::Gray => ColourRGB::new(half, half, half),
NamedColour::Maroon => ColourRGB::new(half, zero, zero), NamedColour::Maroon => ColourRGB::new(half, zero, zero),
NamedColour::Olive => ColourRGB::new(half, half, zero), NamedColour::Olive => ColourRGB::new(half, half, zero),
NamedColour::Green => ColourRGB::new(half, half, half), NamedColour::Green => ColourRGB::new(zero, half, zero),
NamedColour::Purple => ColourRGB::new(half, zero, half), NamedColour::Purple => ColourRGB::new(half, zero, half),
NamedColour::Teal => ColourRGB::new(zero, half, half), NamedColour::Teal => ColourRGB::new(zero, half, half),
NamedColour::Navy => ColourRGB::new(zero, zero, half), NamedColour::Navy => ColourRGB::new(zero, zero, half),
} }
} }
}
impl NormalizedAsByte for f32 { impl NormalizedAsByte for f32 {
fn normalized_to_byte(self) -> u8 { fn normalized_to_byte(self) -> u8 {

View File

@ -1,3 +1,5 @@
use super::colour::ColourRGB24;
pub struct OutputImage { pub struct OutputImage {
pixel_data: Vec<u8>, pixel_data: Vec<u8>,
width: u32, width: u32,
@ -22,12 +24,10 @@ impl OutputImage {
self self
} }
pub fn set_color(&mut self, row: u32, column: u32, red: u8, green: u8, blue: u8) { pub fn set_color(&mut self, row: u32, column: u32, colour: ColourRGB24) {
assert!(row < self.height && column < self.width); assert!(row < self.height && column < self.width);
let index = (((self.height - (row + 1)) * self.width + column) * self.channels) as usize; let index = (((self.height - (row + 1)) * self.width + column) * self.channels) as usize;
self.pixel_data[index] = red; self.pixel_data[index..index+3].copy_from_slice(&colour.values[..]);
self.pixel_data[index + 1] = green;
self.pixel_data[index + 2] = blue;
} }
pub fn get_pixel_data(&self) -> &Vec<u8> { pub fn get_pixel_data(&self) -> &Vec<u8> {

View File

@ -1,26 +1,28 @@
use nalgebra::{RealField, Vector3}; use nalgebra::{RealField, Vector3};
use super::colour::{ColourRGB, NormalizedAsByte};
use super::raycasting::IntersectionInfo; use super::raycasting::IntersectionInfo;
pub trait Integrator<T: RealField> { pub trait Integrator<T: RealField + NormalizedAsByte> {
fn integrate(&self, info: &IntersectionInfo<T>) -> T; fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRGB<T>;
} }
pub struct DirectionalLight<T: RealField> { pub struct DirectionalLight<T: RealField + NormalizedAsByte> {
pub direction: Vector3<T>, pub direction: Vector3<T>,
pub intensity: T, pub intensity: T,
} }
pub struct PhongIntegrator<T: RealField> { pub struct PhongIntegrator<T: RealField + NormalizedAsByte> {
pub ambient_light: T, pub ambient_light: T,
pub lights: Vec<DirectionalLight<T>>, pub lights: Vec<DirectionalLight<T>>,
} }
impl<T: RealField> Integrator<T> for PhongIntegrator<T> { impl<T: RealField + NormalizedAsByte> Integrator<T> for PhongIntegrator<T> {
fn integrate(&self, info: &IntersectionInfo<T>) -> T { fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRGB<T> {
self.lights let intensity = self.lights
.iter() .iter()
.map(|light| light.intensity * light.direction.dot(&info.normal)) .map(|light| light.intensity * light.direction.dot(&info.normal))
.fold(self.ambient_light, |a, b| a + b) .fold(self.ambient_light, |a, b| a + b);
ColourRGB::from_vector3(&(info.material.colour.as_vector3() * intensity))
} }
} }

View File

@ -7,7 +7,9 @@ use std::time::Duration;
use nalgebra::Vector3; use nalgebra::Vector3;
use vanrijn::materials::Material;
use vanrijn::camera::render_scene; use vanrijn::camera::render_scene;
use vanrijn::colour::NamedColour;
use vanrijn::image::OutputImage; use vanrijn::image::OutputImage;
use vanrijn::raycasting::{Plane, Sphere}; use vanrijn::raycasting::{Plane, Sphere};
use vanrijn::scene::Scene; use vanrijn::scene::Scene;
@ -56,8 +58,22 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let scene = Scene { let scene = Scene {
camera_location: Vector3::new(0.0, 0.0, 0.0), camera_location: Vector3::new(0.0, 0.0, 0.0),
objects: vec![ objects: vec![
Box::new(Plane::new(Vector3::new(0.0, 1.0, 0.0), -2.0)), Box::new(Plane::new(
Box::new(Sphere::new(Vector3::new(0.0, 1.0, 5.0), 1.0)), Vector3::new(0.0, 1.0, 0.0),
-2.0,
Material {
colour: NamedColour::Green.as_colourrgb(),
smoothness: 0.0,
},
)),
Box::new(Sphere::new(
Vector3::new(0.0, 1.0, 5.0),
1.0,
Material {
colour: NamedColour::Blue.as_colourrgb(),
smoothness: 0.7,
},
))
], ],
}; };
render_scene(&mut output_image, &scene); render_scene(&mut output_image, &scene);

View File

@ -1,8 +1,18 @@
use nalgebra::RealField; use nalgebra::RealField;
use super::colour::ColourRGB; use super::colour::{ColourRGB, NormalizedAsByte};
pub struct PhongMaterial<T: RealField> { #[derive(Debug)]
pub struct Material<T: RealField> {
pub colour: ColourRGB<T>, pub colour: ColourRGB<T>,
pub smoothness: T, pub smoothness: T,
} }
impl<T: RealField+NormalizedAsByte> Material<T> {
pub fn new_dummy() -> Material<T> {
Material {
colour: ColourRGB::new(T::one(), T::one(), T::one()),
smoothness: T::zero(),
}
}
}

View File

@ -1,5 +1,7 @@
use nalgebra::{convert, RealField, Vector3}; use nalgebra::{convert, RealField, Vector3};
use super::materials::Material;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Ray<T: RealField> { pub struct Ray<T: RealField> {
origin: Vector3<T>, origin: Vector3<T>,
@ -20,11 +22,12 @@ impl<T: RealField> Ray<T> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct IntersectionInfo<T: RealField> { pub struct IntersectionInfo<'a, 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 trait Intersect<T: RealField> { pub trait Intersect<T: RealField> {
@ -34,11 +37,16 @@ pub trait Intersect<T: RealField> {
pub struct Sphere<T: RealField> { pub struct Sphere<T: RealField> {
centre: Vector3<T>, centre: Vector3<T>,
radius: T, radius: T,
material: Material<T>,
} }
impl<T: RealField> Sphere<T> { impl<T: RealField> Sphere<T> {
pub fn new(centre: Vector3<T>, radius: T) -> Sphere<T> { pub fn new(centre: Vector3<T>, radius: T, material: Material<T>) -> Sphere<T> {
Sphere { centre, radius } Sphere {
centre,
radius,
material,
}
} }
} }
@ -76,6 +84,7 @@ impl<T: RealField> Intersect<T> for Sphere<T> {
location, location,
normal, normal,
retro, retro,
material: &self.material,
}) })
} }
} }
@ -83,14 +92,16 @@ 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>,
} }
impl<T: RealField> Plane<T> { impl<T: RealField> Plane<T> {
pub fn new(normal: Vector3<T>, distance_from_origin: T) -> Plane<T> { pub fn new(normal: Vector3<T>, distance_from_origin: T, material: Material<T>) -> Plane<T> {
normal.normalize(); normal.normalize();
Plane { Plane {
normal, normal,
distance_from_origin, distance_from_origin,
material,
} }
} }
} }
@ -117,6 +128,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,
}) })
} }
} }
@ -176,55 +188,56 @@ 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); let s = Sphere::new(Vector3::new(1.5, 1.5, 15.0), 5.0, Material::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); let s = Sphere::new(Vector3::new(-5.0, 1.5, 15.0), 5.0, Material::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); let s = Sphere::new(Vector3::new(1.5, 1.5, -15.0), 5.0, Material::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); let s = Sphere::new(Vector3::new(1.5, 1.5, 2.0), 5.0, Material::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); let p = Plane::new(Vector3::new(1.0, 0.0, 0.0), -5.0, Material::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); let p = Plane::new(Vector3::new(1.0, 0.0, 0.0), -5.0, Material::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); let p = Plane::new(Vector3::new(1.0, 0.0, 0.0), -5.0, Material::new_dummy());
match p.intersect(&r) { match p.intersect(&r) {
Some(IntersectionInfo { Some(IntersectionInfo {
distance: _, distance: _,
location, location,
normal: _, normal: _,
retro: _, retro: _,
material: _,
}) => assert!((location.x - (-5.0f64)).abs() < 0.0000000001), }) => assert!((location.x - (-5.0f64)).abs() < 0.0000000001),
None => panic!(), None => panic!(),
} }