diff --git a/README.md b/README.md index 6581e31..9e985ae 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This list may be changed or extended in the future. - [ ] Point light sources - [x] Point source struct - [x] Point source illuminance test - - [ ] Hard shadows + - [x] Hard shadows - [ ] Soft shadows - [ ] ~~Light-emitting surfaces~~ - [ ] Indirect lighting diff --git a/src/main.rs b/src/main.rs index 9a3e779..b8a341f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ extern crate nalgebra as na; +use std::time::Instant; use std::fs::File; use std::io::Write; use na::*; -use na::geometry::Point3; mod camera; use camera::*; mod types; use types::*; @@ -36,15 +36,24 @@ fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()> fn main() -> std::io::Result<()> { - let camera = Camera::new(Point3::new(0.0,0.0,0.0), Vector3::new(0.0,0.0,1.0), 1.0, 16.0 / 9.0, 2.0, 480); + let camera = Camera::new(Point3::new(0.0,0.0,2.5), Vector3::new(0.0,0.0,-1.0), 1.0, 16.0 / 9.0, 2.0, 720); let scene = Scene { objects: vec![ - Object::new(TriangleMesh::singleton(Point3::new(-1.0, -1.0, 2.0), Point3::new(0.0, 1.0, 2.0), Point3::new(1.0, -1.0, 2.0), |t, u, v| Texture::new(t, u, v, 0.18))) + Object::new(Sphere::new(0.0, 0.0, 0.0, 1.0, |a, b| Texture::new(0.0, a, b, 1.0))) ], - lights: Vec::new(), - background: Color::black() + lights: vec![ + Box::new(PointLight::new(Point3::new(1.0, 0.7, 1.5), Color::white(), 3.0)), + Box::new(PointLight::new(Point3::new(-1.0, -0.3, 0.4), Color::new(1.0, 0.0, 0.0), 4.0)) + ], + background: Color::gray(0.5) }; - render(&camera, &scene, "out.ppm") + let before = Instant::now(); + + render(&camera, &scene, "out.ppm")?; + + println!("{}", before.elapsed().as_millis()); + + Ok(()) } diff --git a/src/object.rs b/src/object.rs index 54f1ebf..114ee0a 100644 --- a/src/object.rs +++ b/src/object.rs @@ -5,8 +5,6 @@ mod triangle; pub use triangle::*; mod bound; pub use bound::*; mod pointlight; pub use pointlight::*; -use na::*; - use crate::types::*; // A trait for types that can be in Objects. @@ -56,8 +54,13 @@ impl Object { pub trait Light { // Determine if the light is able to illuminate the point. - // If so, return the light amount recieved. - fn illuminate(&self, point: Point3f, objects: &Vec) -> Option; + fn check_shadow(&self, point: Point3f, objects: &Vec) -> bool; + + // Compute color on a point. + fn getcolor(&self, point: Point3f) -> Color; + + // Compute intensity on a point. + fn intensity(&self, point: Point3f) -> f32; // Return the direction from the point to the light source. fn direction(&self, point: Point3f) -> Unit3f; @@ -68,17 +71,3 @@ pub struct Scene { pub lights: Vec>, pub background: Color } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn obj_getcolor() { - let sphere = Object::new(Sphere::new_solid(0.0, 0.0, 0.0, 1.0, Color::white())); - - let point = Point3::new(1.0, 0.0, 0.0); - - assert_eq!(sphere.getcolor(point), Color::white()); - } -} diff --git a/src/object/plane.rs b/src/object/plane.rs index 687f955..881e5d6 100644 --- a/src/object/plane.rs +++ b/src/object/plane.rs @@ -91,36 +91,3 @@ impl Surface for Plane { // bounding sphere could possibly contain one. fn bound(&self) -> Bound { Bound::bypass() } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn plane_new() { - let plane = Plane::xy(|_, _| Color::black()); - - assert_eq!(plane.center, Point3::new(0.0, 0.0, 0.0)); - assert_eq!(plane.normal, Unit::new_unchecked(Vector3::z())); - } - - #[test] - fn plane_intersect() { - const N: f32 = 5.0; - let plane = Plane::xz(|_, _| Color::black()); - - let ray = Ray::new(Point3::new(0.0, N, 0.0), Vector3::new(0.0, -1.0, 0.0)); - - assert_eq!(plane.intersect(ray), Some(N)); - } - - #[test] - fn plane_getcolor() { - const N: f32 = 5.0; - let plane = Plane::xz(|x, y| Color::new(x, y, 0.0)); - - let point = Point3::new(5.0, 7.0, 6.0); - - assert_eq!(plane.getcolor(point), Color::new(5.0, 6.0, 0.0)); - } -} diff --git a/src/object/pointlight.rs b/src/object/pointlight.rs index b8f0d1f..2c964dd 100644 --- a/src/object/pointlight.rs +++ b/src/object/pointlight.rs @@ -1,7 +1,6 @@ extern crate nalgebra as na; use na::*; -use na::geometry::Point3; use crate::types::*; use super::*; @@ -12,6 +11,7 @@ pub struct PointLight { pub intensity: f32 } +#[allow(dead_code)] impl PointLight { pub fn new(pos: Point3f, color: Color, intensity: f32) -> PointLight { PointLight { @@ -20,23 +20,35 @@ impl PointLight { intensity: intensity } } - - fn check_point(&self, point: Point3f, objects: &Vec) -> bool { - let max_d = distance(&self.pos, &point); - objects.iter() - .filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point))) - .all(|d| d > max_d) - } } impl Light for PointLight { - fn illuminate(&self, point: Point3f, objects: &Vec) -> Option { - if self.check_point(point, objects) { - Some(self.color) - } else { None } + fn check_shadow(&self, point: Point3f, objects: &Vec) -> bool { + let max_d = distance(&self.pos, &point); + objects.iter() + .filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point))) + .all(|d| d - max_d > -1e-3 ) } + fn getcolor(&self, _point: Point3f) -> Color { self.color } + + fn intensity(&self, _point: Point3f) -> f32 { self.intensity } + fn direction(&self, point: Point3f) -> Unit3f { Unit::new_normalize(self.pos - point) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pointlight_checkshadow() { + let light = PointLight::new(Point3::new(0.0, 1.0, 0.0), Color::white(), 1.0); + let block = Object::new(Sphere::new_solid(0.0, 0.5, 0.0, 0.1, Texture::new(0.0, 0.0, 0.0, 0.0))); + + assert!(light.check_shadow(Point3::origin(), &Vec::new())); + assert!(!light.check_shadow(Point3::origin(), &vec![block])); + } +} diff --git a/src/object/triangle.rs b/src/object/triangle.rs index 39dccd6..1b2a361 100644 --- a/src/object/triangle.rs +++ b/src/object/triangle.rs @@ -241,102 +241,3 @@ impl Surface for TriangleMesh { Bound { center: center, radius: radius + 1e-3, bypass: false } } } - -#[cfg(test)] -mod tests { - use super::*; - - fn roundcolor(color: Color) -> Color { - Color::new((color.red * 100.0).round() / 100.0, (color.green * 100.0).round() / 100.0, (color.blue * 100.0).round() / 100.0) - } - - #[test] - fn triangle_intersect() { - let triangle = TriangleMesh::singleton_solid(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), Color::black()); - - let ray = Ray::new(Point3::new(0.5, 5.0, 0.3), Vector3::new(0.0, -1.0, 0.0)); - - let (t, u, v) = triangle.tris[0].intersect_(&triangle.vertices, ray).unwrap(); - - println!("{},{},{}", t, u, v); - - assert!(t >= 0.0 && t <= 1.0); - assert!(u >= 0.0 && u <= 1.0); - assert!(v >= 0.0 && v <= 1.0); - } - - #[test] - fn triangle_getcolor() { - let triangle = TriangleMesh::singleton(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), |t, u, v| Color::new(t, u, v)); - - let t = 0.4; - let u = 0.1; - let v = 1.0 - t - u; - - let point = triangle.tris[0].from_bary(&triangle.vertices, t, u, v); - - assert_eq!(roundcolor(triangle.getcolor(point)), roundcolor(Color::new(t, u, v))); - } - - #[test] - fn triangle_bounds() { - let point1 = Point3::new(0.0, 0.0, 0.0); - let point2 = Point3::new(1.0, 0.0, 0.0); - let point3 = Point3::new(0.0, 1.0, 0.0); - - let triangle = TriangleMesh::singleton_solid(point1, point2, point3, Color::black()); - - let bound = triangle.bound(); - - println!("{:?}", bound); - - assert!(bound.contains(&point1)); - assert!(bound.contains(&point2)); - assert!(bound.contains(&point3)); - } - /* - #[test] - fn triangle_tobound() { - let point1 = Point3::new(-3.0, 4.0, -6.0); - let point2 = Point3::new(5.0, -2.0, -7.0); - let point3 = Point3::new(9.0, -7.0, 3.0); - - let (center, radius) = triangle_sphere(&point1, &point2, &point3); - let bound = Bound { center: center, radius: radius + 0.01, bypass: false }; - - println!("{:?}", bound); - - println!("{}\n{}\n{}", distance(&bound.center, &point1), - distance(&bound.center, &point2), - distance(&bound.center, &point3)); - - assert!(bound.contains(&point1)); - assert!(bound.contains(&point2)); - assert!(bound.contains(&point3)); - } - - #[test] - fn triangle_tetrabound() { - let point1 = Point3::new(8.0, -2.0, -5.0); - let point2 = Point3::new(-3.0, 4.0, -6.0); - let point3 = Point3::new(-3.0, -9.0, 3.0); - let point4 = Point3::new(-6.0, 5.0, -9.0); - - let (center, radius) = tetrahedron_sphere(&point1, &point2, &point3, &point4); - - let bound = Bound { center: center, radius: radius + 0.01, bypass: false }; - - println!("{:?}", bound); - - println!("{}\n{}\n{}\n{}", distance(&bound.center, &point1), - distance(&bound.center, &point2), - distance(&bound.center, &point3), - distance(&bound.center, &point4)); - - assert!(bound.contains(&point1)); - assert!(bound.contains(&point2)); - assert!(bound.contains(&point3)); - assert!(bound.contains(&point4)); - } - */ -} diff --git a/src/render.rs b/src/render.rs index 7daeab3..a947715 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,5 +1,6 @@ extern crate nalgebra as na; +use std::f32::consts::PI; use std::cmp::Ordering; use na::*; @@ -15,11 +16,26 @@ fn trace(ray: Ray, objects: &Vec) -> Option<(&Object, f32)> { .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) } +fn light_point(objects: &Vec, obj: &Object, point: Point3f, light: &dyn Light) -> Color { + if light.check_shadow(point, objects) { + + let texture = obj.gettexture(point); + + light.getcolor(point) * (texture.albedo / PI) * light.intensity(point) * obj.normal(point).dot(&*light.direction(point)) + } else { + // Point is in shadow + Color::black() + } +} + pub fn cast_ray(ray: Ray, scene: &Scene) -> Color { if let Some((obj, dist)) = trace(ray, &scene.objects) { let point = ray.project(dist); - let surface_texture = obj.gettexture(point); - surface_texture.color + let surface_color = obj.gettexture(point).color; + + scene.lights.iter() + .map(|light| light_point(&scene.objects, obj, point, &**light)) + .fold(Color::black(), |acc, c| acc + c) * surface_color } else { scene.background } }