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

View File

@ -1,5 +1,6 @@
use nalgebra::{clamp, convert, RealField, Vector3};
#[derive(Debug)]
pub struct ColourRGB<T: RealField> {
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 {
self.values[0]
}
@ -83,11 +88,12 @@ pub enum NamedColour {
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 half: T = convert(0.5);
let one: T = convert(1.0);
match colour {
match self {
NamedColour::Black => ColourRGB::new(zero, zero, zero),
NamedColour::White => ColourRGB::new(one, one, one),
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::Maroon => ColourRGB::new(half, zero, 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::Teal => ColourRGB::new(zero, half, half),
NamedColour::Navy => ColourRGB::new(zero, zero, half),
}
}
}
impl NormalizedAsByte for f32 {
fn normalized_to_byte(self) -> u8 {

View File

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

View File

@ -1,26 +1,28 @@
use nalgebra::{RealField, Vector3};
use super::colour::{ColourRGB, NormalizedAsByte};
use super::raycasting::IntersectionInfo;
pub trait Integrator<T: RealField> {
fn integrate(&self, info: &IntersectionInfo<T>) -> T;
pub trait Integrator<T: RealField + NormalizedAsByte> {
fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRGB<T>;
}
pub struct DirectionalLight<T: RealField> {
pub struct DirectionalLight<T: RealField + NormalizedAsByte> {
pub direction: Vector3<T>,
pub intensity: T,
}
pub struct PhongIntegrator<T: RealField> {
pub struct PhongIntegrator<T: RealField + NormalizedAsByte> {
pub ambient_light: T,
pub lights: Vec<DirectionalLight<T>>,
}
impl<T: RealField> Integrator<T> for PhongIntegrator<T> {
fn integrate(&self, info: &IntersectionInfo<T>) -> T {
self.lights
impl<T: RealField + NormalizedAsByte> Integrator<T> for PhongIntegrator<T> {
fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRGB<T> {
let intensity = self.lights
.iter()
.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 vanrijn::materials::Material;
use vanrijn::camera::render_scene;
use vanrijn::colour::NamedColour;
use vanrijn::image::OutputImage;
use vanrijn::raycasting::{Plane, Sphere};
use vanrijn::scene::Scene;
@ -56,8 +58,22 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let scene = Scene {
camera_location: Vector3::new(0.0, 0.0, 0.0),
objects: vec![
Box::new(Plane::new(Vector3::new(0.0, 1.0, 0.0), -2.0)),
Box::new(Sphere::new(Vector3::new(0.0, 1.0, 5.0), 1.0)),
Box::new(Plane::new(
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);

View File

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