Shapes can now be colours other than gray
This commit is contained in:
parent
f193fbf84b
commit
f13b585bfe
|
|
@ -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!(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,26 +88,28 @@ pub enum NamedColour {
|
|||
Navy,
|
||||
}
|
||||
|
||||
pub fn named_colour<T: RealField+NormalizedAsByte>(colour: NamedColour) -> ColourRGB<T> {
|
||||
let zero: T = convert(0.0);
|
||||
let half: T = convert(0.5);
|
||||
let one: T = convert(1.0);
|
||||
match colour {
|
||||
NamedColour::Black => ColourRGB::new(zero, zero, zero),
|
||||
NamedColour::White => ColourRGB::new(one, one, one),
|
||||
NamedColour::Red => ColourRGB::new(one, zero, zero),
|
||||
NamedColour::Lime => ColourRGB::new(zero, one, zero),
|
||||
NamedColour::Blue => ColourRGB::new(zero, zero, one),
|
||||
NamedColour::Yellow => ColourRGB::new(one, one, zero),
|
||||
NamedColour::Cyan => ColourRGB::new(zero, one, one),
|
||||
NamedColour::Magenta => ColourRGB::new(one, zero, one),
|
||||
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::Purple => ColourRGB::new(half, zero, half),
|
||||
NamedColour::Teal => ColourRGB::new(zero, half, half),
|
||||
NamedColour::Navy => ColourRGB::new(zero, zero, half),
|
||||
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 self {
|
||||
NamedColour::Black => ColourRGB::new(zero, zero, zero),
|
||||
NamedColour::White => ColourRGB::new(one, one, one),
|
||||
NamedColour::Red => ColourRGB::new(one, zero, zero),
|
||||
NamedColour::Lime => ColourRGB::new(zero, one, zero),
|
||||
NamedColour::Blue => ColourRGB::new(zero, zero, one),
|
||||
NamedColour::Yellow => ColourRGB::new(one, one, zero),
|
||||
NamedColour::Cyan => ColourRGB::new(zero, one, one),
|
||||
NamedColour::Magenta => ColourRGB::new(one, zero, one),
|
||||
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(zero, half, zero),
|
||||
NamedColour::Purple => ColourRGB::new(half, zero, half),
|
||||
NamedColour::Teal => ColourRGB::new(zero, half, half),
|
||||
NamedColour::Navy => ColourRGB::new(zero, zero, half),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/main.rs
20
src/main.rs
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!(),
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue