From 3b5410aef991e20a34cfdfb2b6d6ca5b0482ed27 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 23 Mar 2020 15:33:43 +0100 Subject: [PATCH 01/67] library: render: scene: add hemisphere sampling This method takes a given normal, and computes a random ray in the unit-hemisphere described by that normal. We use cosine-weighted importance sampling because it leads to better convergence and is a nice micro-optimisation (from four trigonometric operations to only two). --- pathtracer/src/render/utils.rs | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pathtracer/src/render/utils.rs b/pathtracer/src/render/utils.rs index 3f12bfa..2ff48ce 100644 --- a/pathtracer/src/render/utils.rs +++ b/pathtracer/src/render/utils.rs @@ -1,5 +1,7 @@ use crate::Vector; use nalgebra::Unit; +use rand::prelude::thread_rng; +use rand::Rng; pub fn reflected(incident: Unit, normal: Unit) -> Unit { let proj = incident.dot(&normal); @@ -65,3 +67,58 @@ impl RefractionInfo { std::mem::swap(&mut self.old_index, &mut self.new_index) } } + +/// Returns a random ray in the hemisphere described by a normal unit-vector, and the probability +/// to have picked that direction. +#[allow(unused)] // FIXME: remove once used +pub fn sample_hemisphere(normal: Vector) -> (Vector, f32) { + let mut rng = thread_rng(); + let azimuth = rng.gen::() * std::f32::consts::PI * 2.; + // Cosine weighted importance sampling + let cos_elevation: f32 = rng.gen(); + let sin_elevation = f32::sqrt(1. - cos_elevation * cos_elevation); + + let x = sin_elevation * azimuth.cos(); + let y = cos_elevation; + let z = sin_elevation * azimuth.sin(); + + // Calculate orthonormal base, defined by (normalb_b, normal, normal_t) + // Pay attention to degenerate cases when (y, z) is small for use with cross product + let normal_t = if normal.x.abs() > normal.y.abs() { + Vector::new(normal.z, 0., -normal.x).normalize() + } else { + Vector::new(0., -normal.z, normal.y).normalize() + }; + let normal_b = normal.cross(&normal_t); + + // Perform the matrix calculation by hand... + let scattered = Vector::new( + x * normal_b.x + y * normal.x + z * normal_t.x, + x * normal_b.y + y * normal.y + z * normal_t.y, + x * normal_b.z + y * normal.z + z * normal_t.z, + ); + + // The probability to have picked the ray is inversely proportional to cosine of the angle with + // the normal + (scattered, 1. / scattered.dot(&normal)) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn sample_hemisphere_work() { + // NOTE(Bruno): should use some test-case generation for failure-reproduction purposes... + let mut rng = thread_rng(); + for _ in 0..100 { + let normal = Vector::new(rng.gen(), rng.gen(), rng.gen()); + for _ in 0..100 { + let (sample, proportion) = sample_hemisphere(normal); + let cos_angle = normal.dot(&sample); + assert!(cos_angle >= 0.); + assert!(1. / cos_angle - proportion < std::f32::EPSILON); + } + } + } +} From 5c0fc9689eeb4a8df214dcd9ce831674cf7c09d1 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 23 Mar 2020 16:33:02 +0100 Subject: [PATCH 02/67] library: shape: add InterpolatedTriangle type This is a triangle with added normal interpolation at its edges. This is particularly useful when rendering mesh objects. --- pathtracer/src/shape/interpolated_triangle.rs | 153 ++++++++++++++++++ pathtracer/src/shape/mod.rs | 4 + pathtracer/src/shape/triangle.rs | 2 +- 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 pathtracer/src/shape/interpolated_triangle.rs diff --git a/pathtracer/src/shape/interpolated_triangle.rs b/pathtracer/src/shape/interpolated_triangle.rs new file mode 100644 index 0000000..77af206 --- /dev/null +++ b/pathtracer/src/shape/interpolated_triangle.rs @@ -0,0 +1,153 @@ +use super::triangle::Triangle; +use super::Shape; +use crate::{Point, Point2D, Vector}; +use beevee::aabb::AABB; +use beevee::ray::Ray; +use nalgebra::Unit; +use serde::Deserialize; + +/// Represent a triangle with interpolated normals inside the scene. +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub struct InterpolatedTriangle { + #[serde(flatten)] + tri: Triangle, + // FIXME: serialize with unit + normals: [Unit; 3], +} + +impl InterpolatedTriangle { + /// Creates a new `InterpolatedTriangle` from 3 [`Point`]s and 3 [`Vector`]s. + /// + /// [`Point`]: ../../type.Point.html + /// [`Point`]: ../../type.Vector.html + /// + /// # Examples + /// + /// ``` + /// # use pathtracer::shape::InterpolatedTriangle; + /// # use pathtracer::{Point, Vector}; + /// # + /// let t = InterpolatedTriangle::new( + /// Point::new(1.0, 0.0, 0.0), + /// Point::new(0.0, 1.0, 0.0), + /// Point::new(0.0, 0.0, 1.0), + /// Vector::x_axis(), + /// Vector::y_axis(), + /// Vector::z_axis(), + /// ); + /// ``` + pub fn new( + c0: Point, + c1: Point, + c2: Point, + n0: Unit, + n1: Unit, + n2: Unit, + ) -> Self { + InterpolatedTriangle { + tri: Triangle::new(c0, c1, c2), + normals: [n0, n1, n2], + } + } +} + +impl Shape for InterpolatedTriangle { + fn intersect(&self, ray: &Ray) -> Option { + self.tri.intersect(ray) + } + + fn normal(&self, point: &Point) -> Unit { + let (u, v) = { + let c = self.tri.barycentric(point); + (c.x, c.y) + }; + let interpol = self.normals[0].as_ref() * (1. - u - v) + + self.normals[1].as_ref() * u + + self.normals[2].as_ref() * v; + Unit::new_normalize(interpol) + } + + fn project_texel(&self, point: &Point) -> Point2D { + self.tri.project_texel(point) + } + + fn aabb(&self) -> AABB { + self.tri.aabb() + } + + fn centroid(&self) -> Point { + self.tri.centroid() + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn simple_triangle() -> InterpolatedTriangle { + InterpolatedTriangle::new( + Point::origin(), + Point::new(0., 1., 1.), + Point::new(0., 1., 0.), + Vector::x_axis(), + Vector::y_axis(), + Vector::z_axis(), + ) + } + + #[test] + fn normal_interpolation_at_c0_works() { + let triangle = simple_triangle(); + let normal = triangle.normal(&Point::origin()); + assert_eq!(normal, Vector::x_axis()); + } + + #[test] + fn normal_interpolation_at_c1_works() { + let triangle = simple_triangle(); + let normal = triangle.normal(&Point::new(0., 1., 1.)); + assert_eq!(normal, Vector::y_axis()); + } + + #[test] + fn normal_interpolation_at_c2_works() { + let triangle = simple_triangle(); + let normal = triangle.normal(&Point::new(0., 1., 0.)); + assert_eq!(normal, Vector::z_axis()); + } + + #[test] + fn normal_interpolation_at_center_works() { + let triangle = simple_triangle(); + let center = Point::new(0., 2. / 3., 1. / 3.); + let normal = triangle.normal(¢er); + let expected = Unit::new_normalize(Vector::new(1., 1., 1.)); + assert!((normal.as_ref() - expected.as_ref()).magnitude() < 1e-5) + } + + #[test] + fn deserialization_works() { + let yaml = r#" + corners: + - [0.0, 0.0, 0.0] + - [0.0, 1.0, 1.0] + - [0.0, 1.0, 0.0] + normals: + - [1.0, 0.0, 0.0] + - [0.0, 1.0, 0.0] + - [0.0, 0.0, 1.0] + "#; + let triangle: InterpolatedTriangle = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + triangle, + InterpolatedTriangle::new( + Point::origin(), + Point::new(0., 1., 1.), + Point::new(0., 1., 0.), + Vector::x_axis(), + Vector::y_axis(), + Vector::z_axis(), + ) + ) + } +} diff --git a/pathtracer/src/shape/mod.rs b/pathtracer/src/shape/mod.rs index e68a59a..e510e93 100644 --- a/pathtracer/src/shape/mod.rs +++ b/pathtracer/src/shape/mod.rs @@ -18,6 +18,7 @@ use serde::Deserialize; pub enum ShapeEnum { Sphere, Triangle, + InterpolatedTriangle, } /// Represent an abstract shape inside the scene. @@ -51,6 +52,9 @@ impl Intersected for dyn Shape { } } +mod interpolated_triangle; +pub use interpolated_triangle::*; + mod sphere; pub use sphere::*; diff --git a/pathtracer/src/shape/triangle.rs b/pathtracer/src/shape/triangle.rs index 1fe5d25..b539d01 100644 --- a/pathtracer/src/shape/triangle.rs +++ b/pathtracer/src/shape/triangle.rs @@ -38,7 +38,7 @@ impl Triangle { } } - fn barycentric(&self, point: &Point) -> Point2D { + pub(crate) fn barycentric(&self, point: &Point) -> Point2D { let c0_pos = point - self.c0; // P - A = u * (B - A) + v * (C - A) // (C - A) = v0 is c0c2 From 3039607e4f45e5f0533c199908a1102f18ac4849 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Wed, 25 Mar 2020 23:44:04 +0100 Subject: [PATCH 03/67] beevee: bvh: use Accelerated trait for objects This will allow for the use of meshes inside the BVH. Returning the reference to a triangle inside the mesh directly instead of returning the reference to the mesh itself allows for more optimum execution. --- beevee/src/bvh/accelerated.rs | 37 +++++++++++++++++++++++++++++++++++ beevee/src/bvh/intersected.rs | 5 ++++- beevee/src/bvh/mod.rs | 3 +++ beevee/src/bvh/tree.rs | 32 +++++++++++++++++------------- 4 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 beevee/src/bvh/accelerated.rs diff --git a/beevee/src/bvh/accelerated.rs b/beevee/src/bvh/accelerated.rs new file mode 100644 index 0000000..2161148 --- /dev/null +++ b/beevee/src/bvh/accelerated.rs @@ -0,0 +1,37 @@ +use super::Intersected; +use crate::aabb::Bounded; +use crate::ray::Ray; + +/// The trait for any mesh-like object to be used in the [`BVH`]. If your object is not an +/// aggregate, you should instead implement [`Intersected`] which derives this trait automatically. +/// +/// This trait is there to accomodate for aggregate objects inside the [`BVH`]: you can implement a +/// faster look-up of information using a [`BVH`] in a mesh for example, returning directly the +/// reference to a hit triangle. This enables us to return this triangle instead of returning a +/// reference to the whole mesh. +/// +/// [`BVH`]: struct.BVH.html +/// [`Intersected`]: struct.Intersected.html +pub trait Accelerated: Bounded { + /// The type contained in your [`Accelerated`] structure + /// + /// [`Accelerated`]: struct.Accelerated.html + type Output; + + /// Return None if no intersection happens with the ray, or a tuple of distance along the ray + /// and a reference to the object that was hit. + fn intersect(&self, ray: &Ray) -> Option<(f32, &Self::Output)>; +} + +/// The automatic implementation for any [`Intersected`] object to be used in the [`BVH`]. +impl Accelerated for T +where + T: Intersected, +{ + type Output = Self; + + /// Return a reference to `self` when a distance was found. + fn intersect(&self, ray: &Ray) -> Option<(f32, &Self::Output)> { + self.intersect(ray).map(|t| (t, self)) + } +} diff --git a/beevee/src/bvh/intersected.rs b/beevee/src/bvh/intersected.rs index e5a4316..a079e94 100644 --- a/beevee/src/bvh/intersected.rs +++ b/beevee/src/bvh/intersected.rs @@ -1,8 +1,11 @@ use crate::aabb::Bounded; use crate::ray::Ray; -/// The trait for any object to be used in the [`BVH`]. +/// The trait for any object to be used in the [`BVH`]. Its derivation for [`Accelerated`] is +/// automatically derived to return a reference to itself. If this not the intended semantics, see +/// [`Accelerated`]. /// +/// [`Accelerated`]: struct.Accelerated.html /// [`BVH`]: struct.BVH.html pub trait Intersected: Bounded { /// Return None if there is no intersection, or the distance along the ray to the closest diff --git a/beevee/src/bvh/mod.rs b/beevee/src/bvh/mod.rs index 53a389c..14d6a5c 100644 --- a/beevee/src/bvh/mod.rs +++ b/beevee/src/bvh/mod.rs @@ -1,5 +1,8 @@ //! The Boudning Volume Hiearchy +mod accelerated; +pub use accelerated::*; + mod intersected; pub use intersected::*; diff --git a/beevee/src/bvh/tree.rs b/beevee/src/bvh/tree.rs index ae1493f..3e8cd5a 100644 --- a/beevee/src/bvh/tree.rs +++ b/beevee/src/bvh/tree.rs @@ -1,4 +1,4 @@ -use super::Intersected; +use super::Accelerated; use crate::aabb::AABB; use crate::ray::Ray; use crate::Axis; @@ -23,9 +23,9 @@ struct Node { } /// The BVH containing all the objects of type O. -/// This type must implement [`Intersected`]. +/// This type must implement [`Accelerated`]. /// -/// [`Intersected`]: trait.Intersected.html +/// [`Accelerated`]: trait.Accelerated.html #[derive(Clone, Debug, PartialEq)] pub struct BVH { tree: Node, @@ -92,7 +92,7 @@ impl BVH { /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; /// let bvh = BVH::build(spheres); /// ``` - pub fn build(objects: &mut [O]) -> Self { + pub fn build(objects: &mut [O]) -> Self { Self::with_max_capacity(objects, 32) } @@ -157,7 +157,7 @@ impl BVH { /// let spheres: &mut [Sphere] = &mut [Sphere{ center: Point::origin(), radius: 2.5 }]; /// let bvh = BVH::with_max_capacity(spheres, 32); /// ``` - pub fn with_max_capacity(objects: &mut [O], max_cap: usize) -> Self { + pub fn with_max_capacity(objects: &mut [O], max_cap: usize) -> Self { let tree = build_node(objects, 0, objects.len(), max_cap); Self { tree } } @@ -226,8 +226,8 @@ impl BVH { /// let bvh = BVH::with_max_capacity(spheres, 32); /// assert!(bvh.is_sound(spheres)); /// ``` - pub fn is_sound(&self, objects: &[O]) -> bool { - fn check_node(objects: &[O], node: &Node) -> bool { + pub fn is_sound(&self, objects: &[O]) -> bool { + fn check_node(objects: &[O], node: &Node) -> bool { if node.begin > node.end { return false; } @@ -322,17 +322,21 @@ impl BVH { /// assert_eq!(dist, 0.5); /// assert_eq!(obj, &spheres[0]); /// ``` - pub fn walk<'o, O: Intersected>(&self, ray: &Ray, objects: &'o [O]) -> Option<(f32, &'o O)> { + pub fn walk<'o, O: Accelerated>( + &self, + ray: &Ray, + objects: &'o [O], + ) -> Option<(f32, &'o O::Output)> { walk_rec_helper(ray, objects, &self.tree, std::f32::INFINITY) } } -fn walk_rec_helper<'o, O: Intersected>( +fn walk_rec_helper<'o, O: Accelerated>( ray: &Ray, objects: &'o [O], node: &Node, min: f32, -) -> Option<(f32, &'o O)> { +) -> Option<(f32, &'o O::Output)> { use std::cmp::Ordering; match &node.kind { @@ -340,7 +344,7 @@ fn walk_rec_helper<'o, O: Intersected>( NodeEnum::Leaf => objects[node.begin..node.end] .iter() // This turns the Option of an intersection into an Option<(f32, &O)> - .filter_map(|o| o.intersect(ray).map(|d| (d, o))) + .filter_map(|o| o.intersect(ray)) // Discard values that are too far away .filter(|(dist, _)| dist < &min) // Only keep the minimum value, if there is one @@ -382,14 +386,14 @@ fn walk_rec_helper<'o, O: Intersected>( } } -fn bounds_from_slice(objects: &[O]) -> AABB { +fn bounds_from_slice(objects: &[O]) -> AABB { objects .iter() .map(|o| o.aabb()) .fold(AABB::empty(), |acc, other| acc.union(&other)) } -fn build_node(objects: &mut [O], begin: usize, end: usize, max_cap: usize) -> Node { +fn build_node(objects: &mut [O], begin: usize, end: usize, max_cap: usize) -> Node { let aabb = bounds_from_slice(objects); // Don't split nodes under capacity if objects.len() <= max_cap { @@ -437,7 +441,7 @@ fn build_node(objects: &mut [O], begin: usize, end: usize, max_c /// Returns the index at which to split for SAH, the Axis along which to split, and the calculated /// cost. -fn compute_sah( +fn compute_sah( objects: &mut [O], surface: f32, max_cap: usize, From 998838a6fcff21cd21204edd551dddd5f6661978 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 26 Mar 2020 01:03:34 +0100 Subject: [PATCH 04/67] library: use Intersected as a super trait --- pathtracer/src/render/object.rs | 2 +- pathtracer/src/shape/interpolated_triangle.rs | 15 +++-- pathtracer/src/shape/mod.rs | 53 +++++++++------- pathtracer/src/shape/sphere.rs | 63 ++++++++++--------- pathtracer/src/shape/triangle.rs | 45 +++++++------ 5 files changed, 100 insertions(+), 78 deletions(-) diff --git a/pathtracer/src/render/object.rs b/pathtracer/src/render/object.rs index c76b166..deea9a7 100644 --- a/pathtracer/src/render/object.rs +++ b/pathtracer/src/render/object.rs @@ -1,7 +1,7 @@ //! Logic for the scene objects use crate::material::MaterialEnum; -use crate::shape::{Shape, ShapeEnum}; +use crate::shape::ShapeEnum; use crate::texture::TextureEnum; use crate::Point; use beevee::{ diff --git a/pathtracer/src/shape/interpolated_triangle.rs b/pathtracer/src/shape/interpolated_triangle.rs index 77af206..6b63cdd 100644 --- a/pathtracer/src/shape/interpolated_triangle.rs +++ b/pathtracer/src/shape/interpolated_triangle.rs @@ -1,7 +1,8 @@ use super::triangle::Triangle; use super::Shape; use crate::{Point, Point2D, Vector}; -use beevee::aabb::AABB; +use beevee::aabb::{Bounded, AABB}; +use beevee::bvh::Intersected; use beevee::ray::Ray; use nalgebra::Unit; use serde::Deserialize; @@ -52,10 +53,6 @@ impl InterpolatedTriangle { } impl Shape for InterpolatedTriangle { - fn intersect(&self, ray: &Ray) -> Option { - self.tri.intersect(ray) - } - fn normal(&self, point: &Point) -> Unit { let (u, v) = { let c = self.tri.barycentric(point); @@ -70,7 +67,9 @@ impl Shape for InterpolatedTriangle { fn project_texel(&self, point: &Point) -> Point2D { self.tri.project_texel(point) } +} +impl Bounded for InterpolatedTriangle { fn aabb(&self) -> AABB { self.tri.aabb() } @@ -80,6 +79,12 @@ impl Shape for InterpolatedTriangle { } } +impl Intersected for InterpolatedTriangle { + fn intersect(&self, ray: &Ray) -> Option { + self.tri.intersect(ray) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/pathtracer/src/shape/mod.rs b/pathtracer/src/shape/mod.rs index e510e93..69aa181 100644 --- a/pathtracer/src/shape/mod.rs +++ b/pathtracer/src/shape/mod.rs @@ -21,35 +21,42 @@ pub enum ShapeEnum { InterpolatedTriangle, } +// FIXME: this has to be written by hand due to a limitation of `enum_dispatch` on super traits +impl Bounded for ShapeEnum { + fn aabb(&self) -> AABB { + match self { + ShapeEnum::Sphere(s) => s.aabb(), + ShapeEnum::Triangle(s) => s.aabb(), + ShapeEnum::InterpolatedTriangle(s) => s.aabb(), + } + } + + fn centroid(&self) -> Point { + match self { + ShapeEnum::Sphere(s) => s.centroid(), + ShapeEnum::Triangle(s) => s.centroid(), + ShapeEnum::InterpolatedTriangle(s) => s.centroid(), + } + } +} + +impl Intersected for ShapeEnum { + fn intersect(&self, ray: &Ray) -> Option { + match self { + ShapeEnum::Sphere(s) => s.intersect(ray), + ShapeEnum::Triangle(s) => s.intersect(ray), + ShapeEnum::InterpolatedTriangle(s) => s.intersect(ray), + } + } +} + /// Represent an abstract shape inside the scene. #[enum_dispatch::enum_dispatch(ShapeEnum)] -pub trait Shape: std::fmt::Debug { - /// Return the distance at which the object intersects with the ray, or None if it does not. - fn intersect(&self, ray: &Ray) -> Option; +pub trait Shape: std::fmt::Debug + Intersected { /// Return the unit vector corresponding to the normal at this point of the shape. fn normal(&self, point: &Point) -> Unit; /// Project the point from the shape's surface to its texel coordinates. fn project_texel(&self, point: &Point) -> Point2D; - /// Enclose the `Shape` in an axi-aligned bounding-box. - fn aabb(&self) -> AABB; - /// Return the centroid of the shape. - fn centroid(&self) -> Point; -} - -impl Bounded for dyn Shape { - fn aabb(&self) -> AABB { - self.aabb() - } - - fn centroid(&self) -> Point { - self.centroid() - } -} - -impl Intersected for dyn Shape { - fn intersect(&self, ray: &Ray) -> Option { - self.intersect(ray) - } } mod interpolated_triangle; diff --git a/pathtracer/src/shape/sphere.rs b/pathtracer/src/shape/sphere.rs index 8022cae..bbd74a5 100644 --- a/pathtracer/src/shape/sphere.rs +++ b/pathtracer/src/shape/sphere.rs @@ -1,6 +1,7 @@ use super::Shape; use crate::{Point, Point2D, Vector}; -use beevee::aabb::AABB; +use beevee::aabb::{Bounded, AABB}; +use beevee::bvh::Intersected; use beevee::ray::Ray; use nalgebra::Unit; use serde::Deserialize; @@ -38,6 +39,38 @@ impl Sphere { } impl Shape for Sphere { + fn normal(&self, point: &Point) -> Unit { + let delt = if self.inverted { + self.center - point + } else { + point - self.center + }; + Unit::new_normalize(delt) + } + + fn project_texel(&self, point: &Point) -> Point2D { + // Project the sphere on the XY-plane + Point2D::new( + 0.5 + (point.x - self.center.x) / (2. * self.radius), + 0.5 + (point.y - self.center.y) / (2. * self.radius), + ) + } +} + +impl Bounded for Sphere { + fn aabb(&self) -> AABB { + let delt = Vector::new(self.radius, self.radius, self.radius); + let min = self.center - delt; + let max = self.center + delt; + AABB::with_bounds(min, max) + } + + fn centroid(&self) -> Point { + self.center + } +} + +impl Intersected for Sphere { fn intersect(&self, ray: &Ray) -> Option { use std::mem; @@ -67,34 +100,6 @@ impl Shape for Sphere { Some(t_0) } } - - fn normal(&self, point: &Point) -> Unit { - let delt = if self.inverted { - self.center - point - } else { - point - self.center - }; - Unit::new_normalize(delt) - } - - fn project_texel(&self, point: &Point) -> Point2D { - // Project the sphere on the XY-plane - Point2D::new( - 0.5 + (point.x - self.center.x) / (2. * self.radius), - 0.5 + (point.y - self.center.y) / (2. * self.radius), - ) - } - - fn aabb(&self) -> AABB { - let delt = Vector::new(self.radius, self.radius, self.radius); - let min = self.center - delt; - let max = self.center + delt; - AABB::with_bounds(min, max) - } - - fn centroid(&self) -> Point { - self.center - } } #[cfg(test)] diff --git a/pathtracer/src/shape/triangle.rs b/pathtracer/src/shape/triangle.rs index b539d01..0ab3861 100644 --- a/pathtracer/src/shape/triangle.rs +++ b/pathtracer/src/shape/triangle.rs @@ -1,6 +1,7 @@ use super::Shape; use crate::{Point, Point2D, Vector}; -use beevee::aabb::AABB; +use beevee::aabb::{Bounded, AABB}; +use beevee::bvh::Intersected; use beevee::ray::Ray; use nalgebra::Unit; use serde::{Deserialize, Deserializer}; @@ -58,6 +59,29 @@ impl Triangle { } impl Shape for Triangle { + fn normal(&self, _: &Point) -> Unit { + Unit::new_normalize(self.c0c1.cross(&self.c0c2)) + } + + fn project_texel(&self, point: &Point) -> Point2D { + self.barycentric(point) + } +} + +impl Bounded for Triangle { + fn aabb(&self) -> AABB { + AABB::empty() + .grow(&self.c0) + .grow(&(self.c0 + self.c0c1)) + .grow(&(self.c0 + self.c0c2)) + } + + fn centroid(&self) -> Point { + self.c0 + (self.c0c1 + self.c0c2) / 2. + } +} + +impl Intersected for Triangle { fn intersect(&self, ray: &Ray) -> Option { let pvec = ray.direction.cross(&self.c0c2); let det = self.c0c1.dot(&pvec); @@ -88,25 +112,6 @@ impl Shape for Triangle { Some(t) } } - - fn normal(&self, _: &Point) -> Unit { - Unit::new_normalize(self.c0c1.cross(&self.c0c2)) - } - - fn project_texel(&self, point: &Point) -> Point2D { - self.barycentric(point) - } - - fn aabb(&self) -> AABB { - AABB::empty() - .grow(&self.c0) - .grow(&(self.c0 + self.c0c1)) - .grow(&(self.c0 + self.c0c2)) - } - - fn centroid(&self) -> Point { - self.c0 + (self.c0c1 + self.c0c2) / 2. - } } #[derive(Debug, Deserialize)] From e65a2a1f488d050e0c0ea0bc33ac3f91d380c227 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 23 Mar 2020 16:58:29 +0100 Subject: [PATCH 05/67] WIP: add comment about path-tracing --- pathtracer/src/render/scene.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/render/scene.rs index adebd8b..e543389 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -173,6 +173,9 @@ impl Scene { let normal = object.shape.normal(&point); let reflected_ray = reflected(incident_ray, normal); + // FIXME: change this to averaged sampled rays instead of visiting every light ? + // Indeed the path-tracing algorithm is good for calculating the radiance at a point + // But it should be used for reflection and refraction too... let lighting = self.illuminate(point, object_color, &properties, normal, reflected_ray); if properties.refl_trans.is_none() { // Avoid calculating reflection when not needed From 0f6b81e40cf241aa57dfad4234f2de79542eabce Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 23 Mar 2020 22:49:49 +0100 Subject: [PATCH 06/67] WIP: add Mesh, TriangleTexture, TriangleMaterial --- pathtracer/src/material/mod.rs | 4 ++++ pathtracer/src/material/triangle.rs | 31 +++++++++++++++++++++++++++++ pathtracer/src/render/mesh.rs | 8 ++++++++ pathtracer/src/render/mod.rs | 3 +++ pathtracer/src/shape/mesh.rs | 9 +++++++++ pathtracer/src/texture/mod.rs | 4 ++++ pathtracer/src/texture/triangle.rs | 23 +++++++++++++++++++++ 7 files changed, 82 insertions(+) create mode 100644 pathtracer/src/material/triangle.rs create mode 100644 pathtracer/src/render/mesh.rs create mode 100644 pathtracer/src/shape/mesh.rs create mode 100644 pathtracer/src/texture/triangle.rs diff --git a/pathtracer/src/material/mod.rs b/pathtracer/src/material/mod.rs index 699eac3..63ce29a 100644 --- a/pathtracer/src/material/mod.rs +++ b/pathtracer/src/material/mod.rs @@ -13,6 +13,7 @@ use serde::Deserialize; pub enum MaterialEnum { #[serde(rename = "uniform")] UniformMaterial, + TriangleMaterial, } /// Represent the physical light properties of an object in the scene; @@ -22,5 +23,8 @@ pub trait Material: std::fmt::Debug { fn properties(&self, point: Point2D) -> LightProperties; } +mod triangle; +pub use triangle::*; + mod uniform; pub use uniform::*; diff --git a/pathtracer/src/material/triangle.rs b/pathtracer/src/material/triangle.rs new file mode 100644 index 0000000..a9d8adf --- /dev/null +++ b/pathtracer/src/material/triangle.rs @@ -0,0 +1,31 @@ +use super::Material; +use crate::core::{LightProperties, LinearColor, ReflTransEnum}; +use crate::Point2D; +use serde::Deserialize; + +/// Represent a material which interpolates between three points. +#[derive(Debug, PartialEq, Deserialize)] +pub struct TriangleMaterial { + /// The diffuse components. + diffuse: [LinearColor; 3], + /// The specular components. + specular: [LinearColor; 3], + /// The transparency or reflectivity properties, this is not interpolated. + #[serde(flatten)] + pub refl_trans: Option, +} + +impl Material for TriangleMaterial { + fn properties(&self, point: Point2D) -> LightProperties { + let (u, v) = (point.x, point.y); + let diffuse = self.diffuse[0].clone() * (1. - u - v) + + self.diffuse[1].clone() * u + + self.diffuse[2].clone() * v; + let specular = self.specular[0].clone() * (1. - u - v) + + self.specular[1].clone() * u + + self.specular[2].clone() * v; + LightProperties::new(diffuse, specular, self.refl_trans.clone()) + } +} + +// FIXME: tests diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs new file mode 100644 index 0000000..1c79b05 --- /dev/null +++ b/pathtracer/src/render/mesh.rs @@ -0,0 +1,8 @@ +use super::Object; + +/// Represent a mesh of objects. +pub struct Mesh { + /// The shapes composing the mesh + #[allow(unused)] // FIXME: remove when used + shapes: Vec, +} diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/render/mod.rs index 46bdffa..6d969e6 100644 --- a/pathtracer/src/render/mod.rs +++ b/pathtracer/src/render/mod.rs @@ -3,6 +3,9 @@ pub mod light_aggregate; pub use light_aggregate::*; +mod mesh; +pub use mesh::*; + pub mod object; pub use object::*; diff --git a/pathtracer/src/shape/mesh.rs b/pathtracer/src/shape/mesh.rs new file mode 100644 index 0000000..6e1e789 --- /dev/null +++ b/pathtracer/src/shape/mesh.rs @@ -0,0 +1,9 @@ +use super::{InterpolatedTriangle, Shape, Triangle}; +use crate::material::{Material, TriangleMaterial, UniformMaterial}; +use crate::texture::{Texture, TriangleTexture, UniformTexture}; +use crate::Point; +use beevee::{ + aabb::{Bounded, AABB}, + bvh::Intersected, + ray::Ray, +}; diff --git a/pathtracer/src/texture/mod.rs b/pathtracer/src/texture/mod.rs index ca99154..c432fde 100644 --- a/pathtracer/src/texture/mod.rs +++ b/pathtracer/src/texture/mod.rs @@ -13,6 +13,7 @@ use serde::Deserialize; pub enum TextureEnum { #[serde(rename = "uniform")] UniformTexture, + TriangleTexture, } /// Represent an object's texture. @@ -22,5 +23,8 @@ pub trait Texture: std::fmt::Debug { fn texel_color(&self, point: Point2D) -> LinearColor; } +mod triangle; +pub use triangle::*; + mod uniform; pub use uniform::*; diff --git a/pathtracer/src/texture/triangle.rs b/pathtracer/src/texture/triangle.rs new file mode 100644 index 0000000..947bc80 --- /dev/null +++ b/pathtracer/src/texture/triangle.rs @@ -0,0 +1,23 @@ +use super::{uniform::UniformTexture, Texture}; +use crate::core::LinearColor; +use crate::Point2D; +use serde::Deserialize; + +/// Represent a texture which interpolates between three points. +#[derive(Debug, PartialEq, Deserialize)] +pub struct TriangleTexture { + /// The texture at each point + textures: [UniformTexture; 3], +} + +impl Texture for TriangleTexture { + fn texel_color(&self, point: Point2D) -> LinearColor { + let (u, v) = (point.x, point.y); + let sum = self.textures[0].texel_color(point) * (1. - u - v) + + self.textures[1].texel_color(point) * u + + self.textures[2].texel_color(point) * v; + sum / 3. + } +} + +// FIXME: tests From f03880799bc73bbd716eb626e7e0188f02c67e6f Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 26 Mar 2020 18:20:30 +0100 Subject: [PATCH 07/67] library: render: deserialization: error on unknown --- pathtracer/src/render/scene.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/render/scene.rs index e543389..678a5b9 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -299,6 +299,7 @@ impl Scene { } #[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct SerializedScene { camera: Camera, #[serde(default)] From 06783174429fa28fb132724534155e3b5a31007e Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 26 Mar 2020 18:48:48 +0100 Subject: [PATCH 08/67] library: render: scene: deserialize meshes --- pathtracer/src/render/mesh.rs | 7 +++++-- pathtracer/src/render/scene.rs | 13 +++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index 1c79b05..f5ad91c 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -1,8 +1,11 @@ use super::Object; +use serde::Deserialize; /// Represent a mesh of objects. +#[derive(Debug, PartialEq, Deserialize)] pub struct Mesh { /// The shapes composing the mesh - #[allow(unused)] // FIXME: remove when used - shapes: Vec, + pub(crate) shapes: Vec, } + +// FIXME: wavefront mesh deserialized in mesh diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/render/scene.rs index 678a5b9..57b2180 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -1,6 +1,6 @@ //! Scene rendering logic -use super::{light_aggregate::LightAggregate, object::Object, utils::*}; +use super::{light_aggregate::LightAggregate, mesh::Mesh, object::Object, utils::*}; use crate::{ core::{Camera, LightProperties, LinearColor, ReflTransEnum}, material::Material, @@ -307,6 +307,8 @@ struct SerializedScene { #[serde(default)] objects: Vec, #[serde(default)] + meshes: Vec, + #[serde(default)] background: LinearColor, #[serde(default)] aliasing_limit: u32, @@ -317,7 +319,14 @@ struct SerializedScene { } impl From for Scene { - fn from(scene: SerializedScene) -> Self { + fn from(mut scene: SerializedScene) -> Self { + let mut flattened_meshes: Vec = scene + .meshes + .into_iter() + .map(|m| m.shapes) + .flatten() + .collect(); + scene.objects.append(&mut flattened_meshes); Scene::new( scene.camera, scene.lights, From 8727ae9d878121d85f7b1d16d031040bc87794a2 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 26 Mar 2020 19:03:42 +0100 Subject: [PATCH 09/67] library: use #[serde(from)] for Deserialize --- pathtracer/src/core/camera.rs | 15 +++------------ pathtracer/src/light/spot_light.rs | 15 +++------------ pathtracer/src/render/scene.rs | 14 +++----------- pathtracer/src/shape/triangle.rs | 15 +++------------ 4 files changed, 12 insertions(+), 47 deletions(-) diff --git a/pathtracer/src/core/camera.rs b/pathtracer/src/core/camera.rs index 0c9b625..e11ba1b 100644 --- a/pathtracer/src/core/camera.rs +++ b/pathtracer/src/core/camera.rs @@ -2,10 +2,11 @@ use super::film::Film; use crate::{Point, Vector}; -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; /// Represent an abstract camera to observe the scene. -#[derive(Debug, PartialEq)] +#[serde(from = "SerializedCamera")] +#[derive(Debug, PartialEq, Deserialize)] pub struct Camera { /// Where the camera is set in the scene (i.e: its focal point). origin: Point, @@ -140,16 +141,6 @@ impl From for Camera { } } -impl<'de> Deserialize<'de> for Camera { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let cam: SerializedCamera = Deserialize::deserialize(deserializer)?; - Ok(cam.into()) - } -} - #[cfg(test)] mod test { use super::*; diff --git a/pathtracer/src/light/spot_light.rs b/pathtracer/src/light/spot_light.rs index 5fed907..09d67ca 100644 --- a/pathtracer/src/light/spot_light.rs +++ b/pathtracer/src/light/spot_light.rs @@ -2,12 +2,13 @@ use super::{Light, SpatialLight}; use crate::core::LinearColor; use crate::{Point, Vector}; use nalgebra::Unit; -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; /// Represent a light emanating from a directed light-source, outputting rays in a cone. /// /// The illumination cone cannot have an FOV over 180°. -#[derive(Debug, PartialEq)] +#[serde(from = "SerializedSpotLight")] +#[derive(Debug, PartialEq, Deserialize)] pub struct SpotLight { position: Point, direction: Unit, @@ -82,16 +83,6 @@ impl From for SpotLight { } } -impl<'de> Deserialize<'de> for SpotLight { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let cam: SerializedSpotLight = Deserialize::deserialize(deserializer)?; - Ok(cam.into()) - } -} - #[cfg(test)] mod test { use super::*; diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/render/scene.rs index 57b2180..ff61f86 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -13,9 +13,11 @@ use image::RgbImage; use nalgebra::Unit; use rand::prelude::thread_rng; use rand::Rng; -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; /// Represent the scene being rendered. +#[serde(from = "SerializedScene")] +#[derive(Debug, PartialEq, Deserialize)] pub struct Scene { camera: Camera, lights: LightAggregate, @@ -339,16 +341,6 @@ impl From for Scene { } } -impl<'de> Deserialize<'de> for Scene { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let cam: SerializedScene = Deserialize::deserialize(deserializer)?; - Ok(cam.into()) - } -} - #[cfg(test)] mod test { use super::*; diff --git a/pathtracer/src/shape/triangle.rs b/pathtracer/src/shape/triangle.rs index 0ab3861..7786b14 100644 --- a/pathtracer/src/shape/triangle.rs +++ b/pathtracer/src/shape/triangle.rs @@ -4,10 +4,11 @@ use beevee::aabb::{Bounded, AABB}; use beevee::bvh::Intersected; use beevee::ray::Ray; use nalgebra::Unit; -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; /// Represent a triangle inside the scene. -#[derive(Clone, Debug, PartialEq)] +#[serde(from = "SerializedTriangle")] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct Triangle { c0: Point, c0c1: Vector, @@ -129,16 +130,6 @@ impl From for Triangle { } } -impl<'de> Deserialize<'de> for Triangle { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let cam: SerializedTriangle = Deserialize::deserialize(deserializer)?; - Ok(cam.into()) - } -} - #[cfg(test)] mod test { use super::*; From a0d7d5e5903be3e06ebad8af850007bd3263aeed Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 26 Mar 2020 19:03:59 +0100 Subject: [PATCH 10/67] library: render: scene: remove ignored test --- pathtracer/src/render/scene.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/render/scene.rs index ff61f86..8a46cb9 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -73,7 +73,6 @@ impl Scene { reflection_limit: u32, diffraction_index: f32, ) -> Self { - // NOTE(Antoine): fun fact: BVH::build stack overflows when given an empty slice :) let bvh = BVH::build(&mut objects); Scene { camera, @@ -353,8 +352,7 @@ mod test { } #[test] - #[ignore] // stack overflow because of BVH :( - fn bvh_fails() { + fn empty_scene() { use crate::core::Camera; use crate::render::{LightAggregate, Scene}; From 0368edbd74d6b29ea8d971e87bf90944153fd1b6 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 26 Mar 2020 20:55:42 +0100 Subject: [PATCH 11/67] library: render: mesh: add OBJ loading --- pathtracer/Cargo.toml | 1 + pathtracer/src/material/mod.rs | 2 +- pathtracer/src/material/triangle.rs | 2 +- pathtracer/src/render/mesh.rs | 90 ++++++++++++++++++++++++++++- pathtracer/src/render/object.rs | 2 +- pathtracer/src/render/scene.rs | 1 + pathtracer/src/shape/mod.rs | 2 +- pathtracer/src/texture/mod.rs | 2 +- pathtracer/src/texture/triangle.rs | 2 +- 9 files changed, 96 insertions(+), 8 deletions(-) diff --git a/pathtracer/Cargo.toml b/pathtracer/Cargo.toml index dd98d70..4bd7077 100644 --- a/pathtracer/Cargo.toml +++ b/pathtracer/Cargo.toml @@ -28,6 +28,7 @@ rand = "0.7" rayon = "1.3.0" serde_yaml = "0.8" structopt = "0.3" +tobj = "0.1" [dependencies.nalgebra] version = "0.20.0" diff --git a/pathtracer/src/material/mod.rs b/pathtracer/src/material/mod.rs index 63ce29a..879f5e9 100644 --- a/pathtracer/src/material/mod.rs +++ b/pathtracer/src/material/mod.rs @@ -9,7 +9,7 @@ use serde::Deserialize; #[serde(rename_all = "lowercase")] #[allow(missing_docs)] #[enum_dispatch::enum_dispatch] -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub enum MaterialEnum { #[serde(rename = "uniform")] UniformMaterial, diff --git a/pathtracer/src/material/triangle.rs b/pathtracer/src/material/triangle.rs index a9d8adf..72790f2 100644 --- a/pathtracer/src/material/triangle.rs +++ b/pathtracer/src/material/triangle.rs @@ -4,7 +4,7 @@ use crate::Point2D; use serde::Deserialize; /// Represent a material which interpolates between three points. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub struct TriangleMaterial { /// The diffuse components. diffuse: [LinearColor; 3], diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index f5ad91c..9d7fe5d 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -1,11 +1,97 @@ -use super::Object; +use std::convert::TryFrom; +use std::path::PathBuf; + +use nalgebra::Unit; + use serde::Deserialize; +use tobj::{self, load_obj}; + +use super::Object; +use crate::{ + core::{LightProperties, LinearColor}, + material::UniformMaterial, + shape::InterpolatedTriangle, + texture::UniformTexture, + Point, Vector, +}; + /// Represent a mesh of objects. +#[serde(try_from = "Wavefront")] #[derive(Debug, PartialEq, Deserialize)] pub struct Mesh { /// The shapes composing the mesh pub(crate) shapes: Vec, } -// FIXME: wavefront mesh deserialized in mesh +#[derive(Debug, PartialEq, Deserialize)] +pub(crate) struct Wavefront { + pub obj_file: PathBuf, +} + +impl TryFrom for Mesh { + type Error = tobj::LoadError; + + fn try_from(wavefront: Wavefront) -> Result { + let mut shapes = Vec::new(); + + let (models, _materials) = load_obj(&wavefront.obj_file)?; + + for model in models { + let mesh = &model.mesh; + + // mesh.indices contains all vertices. Each group of 3 vertices + // is a triangle, so we iterate over indices 3 by 3. + for i in 0..(mesh.indices.len() / 3) { + let (a, b, c) = ( + mesh.indices[i * 3] as usize, + mesh.indices[i * 3 + 1] as usize, + mesh.indices[i * 3 + 2] as usize, + ); + + // FIXME: world-to-object transformations needed + let pos_a = Point::from_slice(&mesh.positions[(a * 3)..(a * 3 + 2)]); + let pos_b = Point::from_slice(&mesh.positions[(b * 3)..(b * 3 + 2)]); + let pos_c = Point::from_slice(&mesh.positions[(c * 3)..(c * 3 + 2)]); + + // FIXME: normals could be empty + let norm_a = Unit::new_normalize(Vector::new( + mesh.normals[a * 3], + mesh.normals[a * 3 + 1], + mesh.normals[a * 3 + 2], + )); + let norm_b = Unit::new_normalize(Vector::new( + mesh.normals[b * 3], + mesh.normals[b * 3 + 1], + mesh.normals[b * 3 + 2], + )); + let norm_c = Unit::new_normalize(Vector::new( + mesh.normals[c * 3], + mesh.normals[c * 3 + 1], + mesh.normals[c * 3 + 2], + )); + + let t = InterpolatedTriangle::new(pos_a, pos_b, pos_c, norm_a, norm_b, norm_c); + + // FIXME: handle material + if let Some(_) = mesh.material_id { + } else { + // FIXME: should we accept this, and use a default + // Material, or throw a LoadError + shapes.push(Object::new( + t.into(), + UniformMaterial::new(LightProperties::new( + LinearColor::new(1.0, 0.0, 0.0), + LinearColor::new(0.0, 0.0, 0.0), + None, + )) + .into(), + UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), + )); + } + } + } + + Ok(Mesh { shapes }) + } +} diff --git a/pathtracer/src/render/object.rs b/pathtracer/src/render/object.rs index deea9a7..ee2b61f 100644 --- a/pathtracer/src/render/object.rs +++ b/pathtracer/src/render/object.rs @@ -12,7 +12,7 @@ use beevee::{ use serde::Deserialize; /// An object being rendered in the scene. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Object { /// The `Object`'s physical shape pub shape: ShapeEnum, diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/render/scene.rs index 8a46cb9..884322a 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -328,6 +328,7 @@ impl From for Scene { .flatten() .collect(); scene.objects.append(&mut flattened_meshes); + Scene::new( scene.camera, scene.lights, diff --git a/pathtracer/src/shape/mod.rs b/pathtracer/src/shape/mod.rs index 69aa181..4094632 100644 --- a/pathtracer/src/shape/mod.rs +++ b/pathtracer/src/shape/mod.rs @@ -14,7 +14,7 @@ use serde::Deserialize; #[serde(rename_all = "lowercase")] #[allow(missing_docs)] #[enum_dispatch::enum_dispatch] -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub enum ShapeEnum { Sphere, Triangle, diff --git a/pathtracer/src/texture/mod.rs b/pathtracer/src/texture/mod.rs index c432fde..8d130ad 100644 --- a/pathtracer/src/texture/mod.rs +++ b/pathtracer/src/texture/mod.rs @@ -9,7 +9,7 @@ use serde::Deserialize; #[serde(rename_all = "lowercase")] #[allow(missing_docs)] #[enum_dispatch::enum_dispatch] -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub enum TextureEnum { #[serde(rename = "uniform")] UniformTexture, diff --git a/pathtracer/src/texture/triangle.rs b/pathtracer/src/texture/triangle.rs index 947bc80..9393ebc 100644 --- a/pathtracer/src/texture/triangle.rs +++ b/pathtracer/src/texture/triangle.rs @@ -4,7 +4,7 @@ use crate::Point2D; use serde::Deserialize; /// Represent a texture which interpolates between three points. -#[derive(Debug, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub struct TriangleTexture { /// The texture at each point textures: [UniformTexture; 3], From 6ba0f328cd605a1557ae113d4c287ae9a9f418dc Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 26 Mar 2020 22:48:32 +0100 Subject: [PATCH 12/67] library: render: mesh: handle empty normals in OBJ --- pathtracer/src/render/mesh.rs | 84 ++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index 9d7fe5d..c201858 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -10,9 +10,9 @@ use tobj::{self, load_obj}; use super::Object; use crate::{ core::{LightProperties, LinearColor}, - material::UniformMaterial, - shape::InterpolatedTriangle, - texture::UniformTexture, + material::{MaterialEnum, UniformMaterial}, + shape::{InterpolatedTriangle, ShapeEnum, Triangle}, + texture::{TextureEnum, UniformTexture}, Point, Vector, }; @@ -54,41 +54,55 @@ impl TryFrom for Mesh { let pos_b = Point::from_slice(&mesh.positions[(b * 3)..(b * 3 + 2)]); let pos_c = Point::from_slice(&mesh.positions[(c * 3)..(c * 3 + 2)]); - // FIXME: normals could be empty - let norm_a = Unit::new_normalize(Vector::new( - mesh.normals[a * 3], - mesh.normals[a * 3 + 1], - mesh.normals[a * 3 + 2], - )); - let norm_b = Unit::new_normalize(Vector::new( - mesh.normals[b * 3], - mesh.normals[b * 3 + 1], - mesh.normals[b * 3 + 2], - )); - let norm_c = Unit::new_normalize(Vector::new( - mesh.normals[c * 3], - mesh.normals[c * 3 + 1], - mesh.normals[c * 3 + 2], - )); + let triangle: ShapeEnum = if mesh.normals.is_empty() { + Triangle::new(pos_a, pos_b, pos_c).into() + } else { + let norm_a = Unit::new_normalize(Vector::new( + mesh.normals[a * 3], + mesh.normals[a * 3 + 1], + mesh.normals[a * 3 + 2], + )); + let norm_b = Unit::new_normalize(Vector::new( + mesh.normals[b * 3], + mesh.normals[b * 3 + 1], + mesh.normals[b * 3 + 2], + )); + let norm_c = Unit::new_normalize(Vector::new( + mesh.normals[c * 3], + mesh.normals[c * 3 + 1], + mesh.normals[c * 3 + 2], + )); - let t = InterpolatedTriangle::new(pos_a, pos_b, pos_c, norm_a, norm_b, norm_c); + InterpolatedTriangle::new(pos_a, pos_b, pos_c, norm_a, norm_b, norm_c).into() + }; // FIXME: handle material - if let Some(_) = mesh.material_id { - } else { - // FIXME: should we accept this, and use a default - // Material, or throw a LoadError - shapes.push(Object::new( - t.into(), - UniformMaterial::new(LightProperties::new( - LinearColor::new(1.0, 0.0, 0.0), - LinearColor::new(0.0, 0.0, 0.0), - None, - )) - .into(), - UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), - )); - } + let (material, texture): (MaterialEnum, TextureEnum) = + if let Some(_) = mesh.material_id { + ( + UniformMaterial::new(LightProperties::new( + LinearColor::new(1.0, 0.0, 0.0), + LinearColor::new(0.0, 0.0, 0.0), + None, + )) + .into(), + UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), + ) + } else { + // FIXME: should we accept this, and use a default + // Material, or throw a LoadError + ( + UniformMaterial::new(LightProperties::new( + LinearColor::new(1.0, 0.0, 0.0), + LinearColor::new(0.0, 0.0, 0.0), + None, + )) + .into(), + UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), + ) + }; + + shapes.push(Object::new(triangle, material, texture)); } } From fe5eee01721e8c7d0f68900a0de5e6cb5ffb6c54 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 26 Mar 2020 23:16:06 +0100 Subject: [PATCH 13/67] library: render: mesh: load basic material from OBJ --- pathtracer/src/core/color.rs | 19 +++++++++++++++++++ pathtracer/src/render/mesh.rs | 30 +++++++++++++++++++----------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/pathtracer/src/core/color.rs b/pathtracer/src/core/color.rs index 5a90730..b2406ae 100644 --- a/pathtracer/src/core/color.rs +++ b/pathtracer/src/core/color.rs @@ -70,6 +70,25 @@ impl LinearColor { LinearColor { r, g, b } } + /// Creates a new `Color` from a slice. + /// + /// Panics if slice has less than 3 elements. + /// + /// # Examples + /// + /// ``` + /// # use pathtracer::core::LinearColor; + /// # + /// let color = LinearColor::new(&[1.0, 0.0, 0.0]); // bright red! + /// ``` + pub fn from_slice(s: &[f32]) -> Self { + LinearColor { + r: s[0], + g: s[1], + b: s[2], + } + } + #[must_use] /// Clamps the color's RGB components between 0.0 and 1.0. /// diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index c201858..7c3748b 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -35,7 +35,7 @@ impl TryFrom for Mesh { fn try_from(wavefront: Wavefront) -> Result { let mut shapes = Vec::new(); - let (models, _materials) = load_obj(&wavefront.obj_file)?; + let (models, materials) = load_obj(&wavefront.obj_file)?; for model in models { let mesh = &model.mesh; @@ -78,16 +78,24 @@ impl TryFrom for Mesh { // FIXME: handle material let (material, texture): (MaterialEnum, TextureEnum) = - if let Some(_) = mesh.material_id { - ( - UniformMaterial::new(LightProperties::new( - LinearColor::new(1.0, 0.0, 0.0), - LinearColor::new(0.0, 0.0, 0.0), - None, - )) - .into(), - UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), - ) + if let Some(mat_id) = mesh.material_id { + let mesh_mat = &materials[mat_id]; + + let diffuse = LinearColor::from_slice(&mesh_mat.ambient[..]); + let specular = LinearColor::from_slice(&mesh_mat.ambient[..]); + + let material = UniformMaterial::new(LightProperties::new( + diffuse.clone(), + specular, + // FIXME: material.dissolve is supposed to be "the alpha term" + // Needs translation to our ReflTransEnum + None, + )); + + // we only handle uniform textures + let texture = UniformTexture::new(diffuse); + + (material.into(), texture.into()) } else { // FIXME: should we accept this, and use a default // Material, or throw a LoadError From 2d624c517f7c8d0f4b44bd411b52b776b4d37f62 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 26 Mar 2020 23:22:38 +0100 Subject: [PATCH 14/67] library: render: mesh: make default material grey --- pathtracer/src/render/mesh.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index 7c3748b..32dd923 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -101,8 +101,8 @@ impl TryFrom for Mesh { // Material, or throw a LoadError ( UniformMaterial::new(LightProperties::new( - LinearColor::new(1.0, 0.0, 0.0), - LinearColor::new(0.0, 0.0, 0.0), + LinearColor::new(0.5, 0.5, 0.5), + LinearColor::new(0.1, 0.1, 0.1), None, )) .into(), From 15381d4bbd5230fe355900244921da4bce9ccc54 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Fri, 27 Mar 2020 00:54:31 +0100 Subject: [PATCH 15/67] library: render: mesh: scale, rotate, & translate The scaling factor is the same on all axis, to avoid having angles changed which would mess with the normals too much. --- pathtracer/src/render/mesh.rs | 60 ++++++++++++++++++++---------- pathtracer/src/serialize/vector.rs | 5 +++ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index 32dd923..9f9295d 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use std::path::PathBuf; -use nalgebra::Unit; +use nalgebra::{Similarity3, Unit}; use serde::Deserialize; @@ -27,6 +27,12 @@ pub struct Mesh { #[derive(Debug, PartialEq, Deserialize)] pub(crate) struct Wavefront { pub obj_file: PathBuf, + #[serde(default = "crate::serialize::vector::zeros")] + translation: Vector, + #[serde(default = "crate::serialize::vector::zeros")] + rotation: Vector, + #[serde(default = "crate::serialize::coefficient::default_identity")] + scale: f32, } impl TryFrom for Mesh { @@ -37,6 +43,10 @@ impl TryFrom for Mesh { let (models, materials) = load_obj(&wavefront.obj_file)?; + // The object to world transformation matrix + let transform = + Similarity3::new(wavefront.translation, wavefront.rotation, wavefront.scale); + for model in models { let mesh = &model.mesh; @@ -49,29 +59,39 @@ impl TryFrom for Mesh { mesh.indices[i * 3 + 2] as usize, ); - // FIXME: world-to-object transformations needed - let pos_a = Point::from_slice(&mesh.positions[(a * 3)..(a * 3 + 2)]); - let pos_b = Point::from_slice(&mesh.positions[(b * 3)..(b * 3 + 2)]); - let pos_c = Point::from_slice(&mesh.positions[(c * 3)..(c * 3 + 2)]); + let pos_a = transform * Point::from_slice(&mesh.positions[(a * 3)..(a * 3 + 2)]); + let pos_b = transform * Point::from_slice(&mesh.positions[(b * 3)..(b * 3 + 2)]); + let pos_c = transform * Point::from_slice(&mesh.positions[(c * 3)..(c * 3 + 2)]); let triangle: ShapeEnum = if mesh.normals.is_empty() { Triangle::new(pos_a, pos_b, pos_c).into() } else { - let norm_a = Unit::new_normalize(Vector::new( - mesh.normals[a * 3], - mesh.normals[a * 3 + 1], - mesh.normals[a * 3 + 2], - )); - let norm_b = Unit::new_normalize(Vector::new( - mesh.normals[b * 3], - mesh.normals[b * 3 + 1], - mesh.normals[b * 3 + 2], - )); - let norm_c = Unit::new_normalize(Vector::new( - mesh.normals[c * 3], - mesh.normals[c * 3 + 1], - mesh.normals[c * 3 + 2], - )); + // We apply the (arguably useless) scaling to the vectors in case it is + // negative, which would invert their direction + let norm_a = { + let vec = Vector::new( + mesh.normals[a * 3], + mesh.normals[a * 3 + 1], + mesh.normals[a * 3 + 2], + ); + Unit::new_normalize(transform * vec) + }; + let norm_b = { + let vec = Vector::new( + mesh.normals[b * 3], + mesh.normals[b * 3 + 1], + mesh.normals[b * 3 + 2], + ); + Unit::new_normalize(transform * vec) + }; + let norm_c = { + let vec = Vector::new( + mesh.normals[c * 3], + mesh.normals[c * 3 + 1], + mesh.normals[c * 3 + 2], + ); + Unit::new_normalize(transform * vec) + }; InterpolatedTriangle::new(pos_a, pos_b, pos_c, norm_a, norm_b, norm_c).into() }; diff --git a/pathtracer/src/serialize/vector.rs b/pathtracer/src/serialize/vector.rs index 7ffc605..8d02fea 100644 --- a/pathtracer/src/serialize/vector.rs +++ b/pathtracer/src/serialize/vector.rs @@ -14,3 +14,8 @@ where let v: Vector = Deserialize::deserialize(deserializer)?; Ok(Unit::new_normalize(v)) } + +/// Return a vector containing all zeros. +pub fn zeros() -> Vector { + Vector::new(0., 0., 0.) +} From f0d36c7d7b7b057a24654647f68f61ab64d92c87 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 27 Mar 2020 11:13:48 +0100 Subject: [PATCH 16/67] library: render: mesh: use nalgebra::zero instead --- pathtracer/src/render/mesh.rs | 4 ++-- pathtracer/src/serialize/vector.rs | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index 9f9295d..6c41ae3 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -27,9 +27,9 @@ pub struct Mesh { #[derive(Debug, PartialEq, Deserialize)] pub(crate) struct Wavefront { pub obj_file: PathBuf, - #[serde(default = "crate::serialize::vector::zeros")] + #[serde(default = "nalgebra::zero")] translation: Vector, - #[serde(default = "crate::serialize::vector::zeros")] + #[serde(default = "nalgebra::zero")] rotation: Vector, #[serde(default = "crate::serialize::coefficient::default_identity")] scale: f32, diff --git a/pathtracer/src/serialize/vector.rs b/pathtracer/src/serialize/vector.rs index 8d02fea..7ffc605 100644 --- a/pathtracer/src/serialize/vector.rs +++ b/pathtracer/src/serialize/vector.rs @@ -14,8 +14,3 @@ where let v: Vector = Deserialize::deserialize(deserializer)?; Ok(Unit::new_normalize(v)) } - -/// Return a vector containing all zeros. -pub fn zeros() -> Vector { - Vector::new(0., 0., 0.) -} From cca40bcb8e490e2ce5689c566bf93062b5231096 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 27 Mar 2020 12:08:17 +0100 Subject: [PATCH 17/67] library: render: mesh: from_slice to build Vector --- pathtracer/src/render/mesh.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index 6c41ae3..e1ca64c 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use std::path::PathBuf; -use nalgebra::{Similarity3, Unit}; +use nalgebra::{Similarity3, Unit, VectorSlice3}; use serde::Deserialize; @@ -69,27 +69,18 @@ impl TryFrom for Mesh { // We apply the (arguably useless) scaling to the vectors in case it is // negative, which would invert their direction let norm_a = { - let vec = Vector::new( - mesh.normals[a * 3], - mesh.normals[a * 3 + 1], - mesh.normals[a * 3 + 2], - ); + let vec: Vector = + VectorSlice3::from_slice(&mesh.normals[(a * 3)..(a * 3 + 3)]).into(); Unit::new_normalize(transform * vec) }; let norm_b = { - let vec = Vector::new( - mesh.normals[b * 3], - mesh.normals[b * 3 + 1], - mesh.normals[b * 3 + 2], - ); + let vec: Vector = + VectorSlice3::from_slice(&mesh.normals[(b * 3)..(b * 3 + 3)]).into(); Unit::new_normalize(transform * vec) }; let norm_c = { - let vec = Vector::new( - mesh.normals[c * 3], - mesh.normals[c * 3 + 1], - mesh.normals[c * 3 + 2], - ); + let vec: Vector = + VectorSlice3::from_slice(&mesh.normals[(c * 3)..(c * 3 + 3)]).into(); Unit::new_normalize(transform * vec) }; From 0e65a75e2ba0e896509825dd1a3ed1520369b16a Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 27 Mar 2020 12:12:54 +0100 Subject: [PATCH 18/67] library: render: mesh: fix panic when parsing OBJ --- pathtracer/src/render/mesh.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index e1ca64c..e63163b 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -59,9 +59,9 @@ impl TryFrom for Mesh { mesh.indices[i * 3 + 2] as usize, ); - let pos_a = transform * Point::from_slice(&mesh.positions[(a * 3)..(a * 3 + 2)]); - let pos_b = transform * Point::from_slice(&mesh.positions[(b * 3)..(b * 3 + 2)]); - let pos_c = transform * Point::from_slice(&mesh.positions[(c * 3)..(c * 3 + 2)]); + let pos_a = transform * Point::from_slice(&mesh.positions[(a * 3)..(a * 3 + 3)]); + let pos_b = transform * Point::from_slice(&mesh.positions[(b * 3)..(b * 3 + 3)]); + let pos_c = transform * Point::from_slice(&mesh.positions[(c * 3)..(c * 3 + 3)]); let triangle: ShapeEnum = if mesh.normals.is_empty() { Triangle::new(pos_a, pos_b, pos_c).into() From 642f4221cd3f9be00c44870ff1a00fd53cefbd51 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Fri, 27 Mar 2020 17:24:23 +0100 Subject: [PATCH 19/67] beevee: bvh: tree: fix build panic --- beevee/src/bvh/tree.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/beevee/src/bvh/tree.rs b/beevee/src/bvh/tree.rs index 3e8cd5a..8726a4d 100644 --- a/beevee/src/bvh/tree.rs +++ b/beevee/src/bvh/tree.rs @@ -405,7 +405,7 @@ fn build_node(objects: &mut [O], begin: usize, end: usize, max_c }; } // Calculate the SAH heuristic for this slice - let (split, axis, cost) = compute_sah(&mut objects[begin..end], aabb.surface(), max_cap); + let (split, axis, cost) = compute_sah(objects, aabb.surface(), max_cap); // Only split if the heuristic shows that it is worth it if cost >= objects.len() as f32 { return Node { @@ -415,11 +415,11 @@ fn build_node(objects: &mut [O], begin: usize, end: usize, max_c kind: NodeEnum::Leaf, }; } - // Avoid degenerate cases, and recenter the split inside [begin, end) - let split = if split == 0 || split >= (end - begin - 1) { - begin + (end - begin) / 2 + // Avoid degenerate cases + let split = if split <= 1 || split >= (objects.len() - 1) { + (end - begin) / 2 } else { - begin + split + split }; // Project along chosen axis pdqselect::select_by(objects, split, |lhs, rhs| { @@ -428,8 +428,18 @@ fn build_node(objects: &mut [O], begin: usize, end: usize, max_c .expect("Can't use Nans in the SAH computation") }); // Construct children recurivsely on [begin, split) and [split, end) - let left = Box::new(build_node(objects, begin, split, max_cap)); - let right = Box::new(build_node(objects, split, end, max_cap)); + let left = Box::new(build_node( + &mut objects[0..split], + begin, + begin + split, + max_cap, + )); + let right = Box::new(build_node( + &mut objects[split..], + begin + split, + end, + max_cap, + )); // Build the node recursivelly Node { bounds: aabb, @@ -485,7 +495,7 @@ fn compute_sah( let cost = 1. / max_cap as f32 + (left_count as f32 * left_surfaces[left_count - 1] - + right_count as f32 * right_surfaces[right_count]) + + right_count as f32 * right_surfaces[right_count - 1]) / surface; if cost < min { From e1f18786ceeacfc55fc99ba81fe94888844f1497 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 29 Mar 2020 16:52:26 +0200 Subject: [PATCH 20/67] cargo: bump tobj to 1.0 --- pathtracer/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pathtracer/Cargo.toml b/pathtracer/Cargo.toml index 4bd7077..f80ec3b 100644 --- a/pathtracer/Cargo.toml +++ b/pathtracer/Cargo.toml @@ -28,7 +28,7 @@ rand = "0.7" rayon = "1.3.0" serde_yaml = "0.8" structopt = "0.3" -tobj = "0.1" +tobj = "1.0" [dependencies.nalgebra] version = "0.20.0" From 2994a7dcfa7028e64d8bce2fa96281f5bd864b61 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 18:13:18 +0200 Subject: [PATCH 21/67] library: render: mesh: parse rotation in degrees --- pathtracer/src/render/mesh.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/render/mesh.rs index e63163b..3080c9c 100644 --- a/pathtracer/src/render/mesh.rs +++ b/pathtracer/src/render/mesh.rs @@ -44,8 +44,11 @@ impl TryFrom for Mesh { let (models, materials) = load_obj(&wavefront.obj_file)?; // The object to world transformation matrix - let transform = - Similarity3::new(wavefront.translation, wavefront.rotation, wavefront.scale); + let transform = Similarity3::new( + wavefront.translation, + wavefront.rotation * std::f32::consts::PI / 180., // From degrees to radians + wavefront.scale, + ); for model in models { let mesh = &model.mesh; From c7fec074c2db7486bfff21253baf66fd211d84d0 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 29 Mar 2020 19:21:18 +0200 Subject: [PATCH 22/67] examples: add cornell box example --- pathtracer/examples/cornell-box.mtl | 88 ++++++++++++++ pathtracer/examples/cornell-box.obj | 168 +++++++++++++++++++++++++++ pathtracer/examples/cornell-box.yaml | 22 ++++ 3 files changed, 278 insertions(+) create mode 100644 pathtracer/examples/cornell-box.mtl create mode 100644 pathtracer/examples/cornell-box.obj create mode 100644 pathtracer/examples/cornell-box.yaml diff --git a/pathtracer/examples/cornell-box.mtl b/pathtracer/examples/cornell-box.mtl new file mode 100644 index 0000000..3376bb7 --- /dev/null +++ b/pathtracer/examples/cornell-box.mtl @@ -0,0 +1,88 @@ +# The original Cornell Box in OBJ format. +# Note that the real box is not a perfect cube, so +# the faces are imperfect in this data set. +# +# Created by Guedis Cardenas and Morgan McGuire at Williams College, 2011 +# Released into the Public Domain. +# +# http://graphics.cs.williams.edu/data +# http://www.graphics.cornell.edu/online/box/data.html +# + +newmtl leftWall + Ns 10.0000 + Ni 1.5000 + illum 2 + Ka 0.63 0.065 0.05 # Red + Kd 0.63 0.065 0.05 + Ks 0 0 0 + Ke 0 0 0 + + +newmtl rightWall + Ns 10.0000 + Ni 1.5000 + illum 2 + Ka 0.14 0.45 0.091 # Green + Kd 0.14 0.45 0.091 + Ks 0 0 0 + Ke 0 0 0 + + +newmtl floor + Ns 10.0000 + Ni 1.0000 + illum 2 + Ka 0.725 0.71 0.68 # White + Kd 0.725 0.71 0.68 + Ks 0 0 0 + Ke 0 0 0 + + +newmtl ceiling + Ns 10.0000 + Ni 1.0000 + illum 2 + Ka 0.725 0.71 0.68 # White + Kd 0.725 0.71 0.68 + Ks 0 0 0 + Ke 0 0 0 + + +newmtl backWall + Ns 10.0000 + Ni 1.0000 + illum 2 + Ka 0.725 0.71 0.68 # White + Kd 0.725 0.71 0.68 + Ks 0 0 0 + Ke 0 0 0 + + +newmtl shortBox + Ns 10.0000 + Ni 1.0000 + illum 2 + Ka 0.725 0.71 0.68 # White + Kd 0.725 0.71 0.68 + Ks 0 0 0 + Ke 0 0 0 + + +newmtl tallBox + Ns 10.0000 + Ni 1.0000 + illum 2 + Ka 0.725 0.71 0.68 # White + Kd 0.725 0.71 0.68 + Ks 0 0 0 + Ke 0 0 0 + +newmtl light + Ns 10.0000 + Ni 1.0000 + illum 2 + Ka 0.78 0.78 0.78 # White + Kd 0.78 0.78 0.78 + Ks 0 0 0 + Ke 17 12 4 diff --git a/pathtracer/examples/cornell-box.obj b/pathtracer/examples/cornell-box.obj new file mode 100644 index 0000000..1be0aed --- /dev/null +++ b/pathtracer/examples/cornell-box.obj @@ -0,0 +1,168 @@ +# The original Cornell Box in OBJ format. +# Note that the real box is not a perfect cube, so +# the faces are imperfect in this data set. +# +# Created by Guedis Cardenas and Morgan McGuire at Williams College, 2011 +# Released into the Public Domain. +# +# http://graphics.cs.williams.edu/data +# http://www.graphics.cornell.edu/online/box/data.html +# + +mtllib cornell-box.mtl + +## Object floor +v -1.01 0.00 0.99 +v 1.00 0.00 0.99 +v 1.00 0.00 -1.04 +v -0.99 0.00 -1.04 + +g floor +usemtl floor +f -4 -3 -2 -1 + +## Object ceiling +v -1.02 1.99 0.99 +v -1.02 1.99 -1.04 +v 1.00 1.99 -1.04 +v 1.00 1.99 0.99 + +g ceiling +usemtl ceiling +f -4 -3 -2 -1 + +## Object backwall +v -0.99 0.00 -1.04 +v 1.00 0.00 -1.04 +v 1.00 1.99 -1.04 +v -1.02 1.99 -1.04 + +g backWall +usemtl backWall +f -4 -3 -2 -1 + +## Object rightwall +v 1.00 0.00 -1.04 +v 1.00 0.00 0.99 +v 1.00 1.99 0.99 +v 1.00 1.99 -1.04 + +g rightWall +usemtl rightWall +f -4 -3 -2 -1 + +## Object leftWall +v -1.01 0.00 0.99 +v -0.99 0.00 -1.04 +v -1.02 1.99 -1.04 +v -1.02 1.99 0.99 + +g leftWall +usemtl leftWall +f -4 -3 -2 -1 + +## Object shortBox +usemtl shortBox + +# Top Face +v 0.53 0.60 0.75 +v 0.70 0.60 0.17 +v 0.13 0.60 0.00 +v -0.05 0.60 0.57 +f -4 -3 -2 -1 + +# Left Face +v -0.05 0.00 0.57 +v -0.05 0.60 0.57 +v 0.13 0.60 0.00 +v 0.13 0.00 0.00 +f -4 -3 -2 -1 + +# Front Face +v 0.53 0.00 0.75 +v 0.53 0.60 0.75 +v -0.05 0.60 0.57 +v -0.05 0.00 0.57 +f -4 -3 -2 -1 + +# Right Face +v 0.70 0.00 0.17 +v 0.70 0.60 0.17 +v 0.53 0.60 0.75 +v 0.53 0.00 0.75 +f -4 -3 -2 -1 + +# Back Face +v 0.13 0.00 0.00 +v 0.13 0.60 0.00 +v 0.70 0.60 0.17 +v 0.70 0.00 0.17 +f -4 -3 -2 -1 + +# Bottom Face +v 0.53 0.00 0.75 +v 0.70 0.00 0.17 +v 0.13 0.00 0.00 +v -0.05 0.00 0.57 +f -12 -11 -10 -9 + +g shortBox +usemtl shortBox + +## Object tallBox +usemtl tallBox + +# Top Face +v -0.53 1.20 0.09 +v 0.04 1.20 -0.09 +v -0.14 1.20 -0.67 +v -0.71 1.20 -0.49 +f -4 -3 -2 -1 + +# Left Face +v -0.53 0.00 0.09 +v -0.53 1.20 0.09 +v -0.71 1.20 -0.49 +v -0.71 0.00 -0.49 +f -4 -3 -2 -1 + +# Back Face +v -0.71 0.00 -0.49 +v -0.71 1.20 -0.49 +v -0.14 1.20 -0.67 +v -0.14 0.00 -0.67 +f -4 -3 -2 -1 + +# Right Face +v -0.14 0.00 -0.67 +v -0.14 1.20 -0.67 +v 0.04 1.20 -0.09 +v 0.04 0.00 -0.09 +f -4 -3 -2 -1 + +# Front Face +v 0.04 0.00 -0.09 +v 0.04 1.20 -0.09 +v -0.53 1.20 0.09 +v -0.53 0.00 0.09 +f -4 -3 -2 -1 + +# Bottom Face +v -0.53 0.00 0.09 +v 0.04 0.00 -0.09 +v -0.14 0.00 -0.67 +v -0.71 0.00 -0.49 +f -8 -7 -6 -5 + +g tallBox +usemtl tallBox + +## Object light +v -0.24 1.98 0.16 +v -0.24 1.98 -0.22 +v 0.23 1.98 -0.22 +v 0.23 1.98 0.16 + +g light +usemtl light +f -4 -3 -2 -1 diff --git a/pathtracer/examples/cornell-box.yaml b/pathtracer/examples/cornell-box.yaml new file mode 100644 index 0000000..ffcda34 --- /dev/null +++ b/pathtracer/examples/cornell-box.yaml @@ -0,0 +1,22 @@ +reflection_limit: 5 + +camera: + origin: [0.0, 1.0, 0.0] + forward: [ 0.0, 0.0, 1.0] + up: [0.0, 1.0, 0.0] + fov: 90.0 + distance_to_image: 1.0 + x: 2160 + y: 2160 + +lights: + ambients: + - color: {r: 0.1, g: 0.1, b: 0.1} + points: + - position: [-0.5, 1.0, 1.8] + color: {r: 1.0, g: 1.0, b: 1.0} + +meshes: + - obj_file: "pathtracer/examples/cornell-box.obj" + translation: [0.0, 0.0, 1.0] + rotation: [0, 180, 0] From b79c94aad9f987370d657d520a2cdd132c7aaf29 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 19:16:07 +0200 Subject: [PATCH 23/67] library: core: camera: add ray calculation method --- pathtracer/src/core/camera.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pathtracer/src/core/camera.rs b/pathtracer/src/core/camera.rs index e11ba1b..7564101 100644 --- a/pathtracer/src/core/camera.rs +++ b/pathtracer/src/core/camera.rs @@ -2,6 +2,8 @@ use super::film::Film; use crate::{Point, Vector}; +use beevee::ray::Ray; +use nalgebra::Unit; use serde::Deserialize; /// Represent an abstract camera to observe the scene. @@ -79,6 +81,24 @@ impl Camera { pub fn origin(&self) -> &Point { &self.origin } + + /// Get the Ray coming out of the camera at a given ratio on the image. + /// + /// # Examples + /// + /// ``` + /// # use pathtracer::core::Camera; + /// # use pathtracer::Point; + /// # + /// let cam = Camera::default(); + /// let ray_ul = cam.ray_with_ratio(0., 0.); // Ray coming out of the upper-left pixel + /// let ray_ul = cam.ray_with_ratio(1., 1.); // Ray coming out of the lower-right pixel + /// ``` + pub fn ray_with_ratio(&self, x: f32, y: f32) -> Ray { + let pixel = self.film().pixel_at_ratio(x, y); + let direction = Unit::new_normalize(pixel - self.origin()); + Ray::new(pixel, direction) + } } impl Default for Camera { From 78d59544199b8401cf8c115966771338e6c695f2 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 19:21:35 +0200 Subject: [PATCH 24/67] library: core: color: fix from_slice's doc-test --- pathtracer/src/core/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pathtracer/src/core/color.rs b/pathtracer/src/core/color.rs index b2406ae..e6d83c3 100644 --- a/pathtracer/src/core/color.rs +++ b/pathtracer/src/core/color.rs @@ -79,7 +79,7 @@ impl LinearColor { /// ``` /// # use pathtracer::core::LinearColor; /// # - /// let color = LinearColor::new(&[1.0, 0.0, 0.0]); // bright red! + /// let color = LinearColor::from_slice(&[1.0, 0.0, 0.0]); // bright red! /// ``` pub fn from_slice(s: &[f32]) -> Self { LinearColor { From 2f3224ea0764dcff08bb5e0bebc58f1bf5b04cf7 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 19:33:23 +0200 Subject: [PATCH 25/67] library: render: scene: calculate rays w/ camera --- pathtracer/src/render/scene.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/render/scene.rs index 884322a..ece4308 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/render/scene.rs @@ -123,16 +123,15 @@ impl Scene { /// Get pixel color for (x, y) a pixel **coordinate** fn pixel(&self, x: f32, y: f32) -> LinearColor { let (x, y) = self.camera.film().pixel_ratio(x, y); - let pixel = self.camera.film().pixel_at_ratio(x, y); - let direction = Unit::new_normalize(pixel - self.camera.origin()); let indices = RefractionInfo::with_index(self.diffraction_index); - self.cast_ray(Ray::new(pixel, direction)).map_or_else( + let ray = self.camera.ray_with_ratio(x, y); + self.cast_ray(ray).map_or_else( || self.background.clone(), |(t, obj)| { self.color_at( - pixel + direction.as_ref() * t, + ray.origin + ray.direction.as_ref() * t, obj, - direction, + ray.direction, self.reflection_limit, indices, ) From 9ad1100dedba2d29fef013fda8891f501be8c0e2 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 19:24:25 +0200 Subject: [PATCH 26/67] library: core: camera: move film behind the camera To prepare for adding the handling of focal blur, move the film so that it is behind the point of convergence of the lens. In addition, store the distance to the focal plane in the camera, which will be used when calculating rays with an non-zero aperture. --- pathtracer/src/core/camera.rs | 37 ++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/pathtracer/src/core/camera.rs b/pathtracer/src/core/camera.rs index 7564101..8bc7f51 100644 --- a/pathtracer/src/core/camera.rs +++ b/pathtracer/src/core/camera.rs @@ -12,6 +12,8 @@ use serde::Deserialize; pub struct Camera { /// Where the camera is set in the scene (i.e: its focal point). origin: Point, + /// How far away is the camera's plan of focus. + distance_to_image: f32, /// The film to represent each pixel in the scene. film: Film, } @@ -40,15 +42,20 @@ impl Camera { forward: Vector, up: Vector, fov: f32, - dist_to_image: f32, + distance_to_image: f32, x: u32, y: u32, ) -> Self { let right = forward.cross(&up); - let center = origin + forward.normalize() * dist_to_image; - let screen_size = 2. * f32::tan(fov / 2.) * dist_to_image; - let film = Film::new(x, y, screen_size, center, up, right); - Camera { origin, film } + let screen_size = 2. * f32::tan(fov / 2.); + // Construct the film behind the camera, upside down + let center = origin - forward.normalize(); + let film = Film::new(x, y, screen_size, center, -up, -right); + Camera { + origin, + distance_to_image, + film, + } } /// Get the `Camera`'s [`Film`]. @@ -96,7 +103,7 @@ impl Camera { /// ``` pub fn ray_with_ratio(&self, x: f32, y: f32) -> Ray { let pixel = self.film().pixel_at_ratio(x, y); - let direction = Unit::new_normalize(pixel - self.origin()); + let direction = Unit::new_normalize(self.origin() - pixel); Ray::new(pixel, direction) } } @@ -168,7 +175,7 @@ mod test { #[test] fn new_works() { let cam = Camera::new( - Point::new(-1., 0., 0.), + Point::new(1., 0., 0.), Vector::new(1., 0., 0.), Vector::new(0., 1., 0.), 2. * f32::atan(1.), /* 90° in radian */ @@ -179,14 +186,15 @@ mod test { assert_eq!( cam, Camera { - origin: Point::new(-1., 0., 0.), + origin: Point::new(1., 0., 0.), + distance_to_image: 1., film: Film::new( 1080, 1080, 2., Point::origin(), - Vector::new(0., 1., 0.), - Vector::new(0., 0., 1.), + -Vector::new(0., 1., 0.), + -Vector::new(0., 0., 1.), ) } ) @@ -195,7 +203,7 @@ mod test { #[test] fn deserialization_works() { let yaml = r#" - origin: [-1.0, 0.0, 0.0] + origin: [1.0, 0.0, 0.0] forward: [ 1.0, 0.0, 0.0] up: [0.0, 1.0, 0.0] fov: 90.0 @@ -207,14 +215,15 @@ mod test { assert_eq!( cam, Camera { - origin: Point::new(-1., 0., 0.), + origin: Point::new(1., 0., 0.), + distance_to_image: 1.0, film: Film::new( 1080, 1080, 2., Point::origin(), - Vector::new(0., 1., 0.), - Vector::new(0., 0., 1.), + -Vector::new(0., 1., 0.), + -Vector::new(0., 0., 1.), ) } ) From 4593e276c4cd6d6c2fae44181812be00bb26c986 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 19:32:44 +0200 Subject: [PATCH 27/67] library: core: camera: fix documentation phrasing --- pathtracer/src/core/camera.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pathtracer/src/core/camera.rs b/pathtracer/src/core/camera.rs index 8bc7f51..3fa91d6 100644 --- a/pathtracer/src/core/camera.rs +++ b/pathtracer/src/core/camera.rs @@ -10,9 +10,9 @@ use serde::Deserialize; #[serde(from = "SerializedCamera")] #[derive(Debug, PartialEq, Deserialize)] pub struct Camera { - /// Where the camera is set in the scene (i.e: its focal point). + /// Where the camera is set in the scene (i.e: the center of the lens). origin: Point, - /// How far away is the camera's plan of focus. + /// How far away is the camera's focal plane. distance_to_image: f32, /// The film to represent each pixel in the scene. film: Film, From aa47b54e4c4ca02514e642032d68bbbb9c3a4a7d Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 19:44:12 +0200 Subject: [PATCH 28/67] examples: move OBJ-related files to subdirectory --- pathtracer/examples/cornell-box.yaml | 4 +++- pathtracer/examples/{ => objs}/cornell-box.mtl | 0 pathtracer/examples/{ => objs}/cornell-box.obj | 0 3 files changed, 3 insertions(+), 1 deletion(-) rename pathtracer/examples/{ => objs}/cornell-box.mtl (100%) rename pathtracer/examples/{ => objs}/cornell-box.obj (100%) diff --git a/pathtracer/examples/cornell-box.yaml b/pathtracer/examples/cornell-box.yaml index ffcda34..d6fa9bb 100644 --- a/pathtracer/examples/cornell-box.yaml +++ b/pathtracer/examples/cornell-box.yaml @@ -17,6 +17,8 @@ lights: color: {r: 1.0, g: 1.0, b: 1.0} meshes: - - obj_file: "pathtracer/examples/cornell-box.obj" + # FIXME: make the path relative to the YAML in some way? + # Easiest solution would be to chdir to the YAML's directory + - obj_file: "pathtracer/examples/objs/cornell-box.obj" translation: [0.0, 0.0, 1.0] rotation: [0, 180, 0] diff --git a/pathtracer/examples/cornell-box.mtl b/pathtracer/examples/objs/cornell-box.mtl similarity index 100% rename from pathtracer/examples/cornell-box.mtl rename to pathtracer/examples/objs/cornell-box.mtl diff --git a/pathtracer/examples/cornell-box.obj b/pathtracer/examples/objs/cornell-box.obj similarity index 100% rename from pathtracer/examples/cornell-box.obj rename to pathtracer/examples/objs/cornell-box.obj From a59bd026bc02d96c2ad163c1c2e6bc85d65abdb3 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 20:15:27 +0200 Subject: [PATCH 29/67] library: rename 'render' module to 'scene' --- pathtracer/src/lib.rs | 2 +- pathtracer/src/main.rs | 2 +- pathtracer/src/{render => scene}/light_aggregate.rs | 4 ++-- pathtracer/src/{render => scene}/mesh.rs | 0 pathtracer/src/{render => scene}/mod.rs | 0 pathtracer/src/{render => scene}/object.rs | 2 +- pathtracer/src/{render => scene}/scene.rs | 4 ++-- pathtracer/src/{render => scene}/utils.rs | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename pathtracer/src/{render => scene}/light_aggregate.rs (97%) rename pathtracer/src/{render => scene}/mesh.rs (100%) rename pathtracer/src/{render => scene}/mod.rs (100%) rename pathtracer/src/{render => scene}/object.rs (98%) rename pathtracer/src/{render => scene}/scene.rs (99%) rename pathtracer/src/{render => scene}/utils.rs (100%) diff --git a/pathtracer/src/lib.rs b/pathtracer/src/lib.rs index e601b3d..200dd50 100644 --- a/pathtracer/src/lib.rs +++ b/pathtracer/src/lib.rs @@ -11,7 +11,7 @@ pub type Point2D = nalgebra::Point2; pub mod core; pub mod light; pub mod material; -pub mod render; +pub mod scene; pub mod serialize; pub mod shape; pub mod texture; diff --git a/pathtracer/src/main.rs b/pathtracer/src/main.rs index b07c149..3425e73 100644 --- a/pathtracer/src/main.rs +++ b/pathtracer/src/main.rs @@ -1,4 +1,4 @@ -use pathtracer::render::Scene; +use pathtracer::scene::Scene; use std::path::PathBuf; use structopt::StructOpt; diff --git a/pathtracer/src/render/light_aggregate.rs b/pathtracer/src/scene/light_aggregate.rs similarity index 97% rename from pathtracer/src/render/light_aggregate.rs rename to pathtracer/src/scene/light_aggregate.rs index 48070c2..acd0a82 100644 --- a/pathtracer/src/render/light_aggregate.rs +++ b/pathtracer/src/scene/light_aggregate.rs @@ -23,7 +23,7 @@ impl LightAggregate { /// # Examples /// /// ``` - /// # use pathtracer::render::LightAggregate; + /// # use pathtracer::scene::LightAggregate; /// # /// let la = LightAggregate::empty(); /// assert_eq!(la.ambient_lights_iter().count(), 0); @@ -40,7 +40,7 @@ impl LightAggregate { /// # Examples /// /// ``` - /// # use pathtracer::render::LightAggregate; + /// # use pathtracer::scene::LightAggregate; /// # /// let la = LightAggregate::new( /// Vec::new(), diff --git a/pathtracer/src/render/mesh.rs b/pathtracer/src/scene/mesh.rs similarity index 100% rename from pathtracer/src/render/mesh.rs rename to pathtracer/src/scene/mesh.rs diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/scene/mod.rs similarity index 100% rename from pathtracer/src/render/mod.rs rename to pathtracer/src/scene/mod.rs diff --git a/pathtracer/src/render/object.rs b/pathtracer/src/scene/object.rs similarity index 98% rename from pathtracer/src/render/object.rs rename to pathtracer/src/scene/object.rs index ee2b61f..21720d9 100644 --- a/pathtracer/src/render/object.rs +++ b/pathtracer/src/scene/object.rs @@ -30,7 +30,7 @@ impl Object { /// ``` /// # use pathtracer::core::{LightProperties, LinearColor}; /// # use pathtracer::material::UniformMaterial; - /// # use pathtracer::render::Object; + /// # use pathtracer::scene::Object; /// # use pathtracer::shape::Sphere; /// # use pathtracer::texture::UniformTexture; /// # use pathtracer::Point; diff --git a/pathtracer/src/render/scene.rs b/pathtracer/src/scene/scene.rs similarity index 99% rename from pathtracer/src/render/scene.rs rename to pathtracer/src/scene/scene.rs index ece4308..f0fb60b 100644 --- a/pathtracer/src/render/scene.rs +++ b/pathtracer/src/scene/scene.rs @@ -37,7 +37,7 @@ impl Scene { /// ``` /// # use pathtracer::core::{Camera, LightProperties, LinearColor}; /// # use pathtracer::material::UniformMaterial; - /// # use pathtracer::render::{LightAggregate, Object, Scene}; + /// # use pathtracer::scene::{LightAggregate, Object, Scene}; /// # use pathtracer::shape::Sphere; /// # use pathtracer::texture::UniformTexture; /// # use pathtracer::Point; @@ -354,7 +354,7 @@ mod test { #[test] fn empty_scene() { use crate::core::Camera; - use crate::render::{LightAggregate, Scene}; + use crate::scene::{LightAggregate, Scene}; let _scene = Scene::new( Camera::default(), diff --git a/pathtracer/src/render/utils.rs b/pathtracer/src/scene/utils.rs similarity index 100% rename from pathtracer/src/render/utils.rs rename to pathtracer/src/scene/utils.rs From ad668251d4c378841f3e85886e3243659d66c66a Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 20:20:01 +0200 Subject: [PATCH 30/67] beevee: bvh: accelerated: add missing link to BVH --- beevee/src/bvh/accelerated.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beevee/src/bvh/accelerated.rs b/beevee/src/bvh/accelerated.rs index 2161148..ee75686 100644 --- a/beevee/src/bvh/accelerated.rs +++ b/beevee/src/bvh/accelerated.rs @@ -24,6 +24,8 @@ pub trait Accelerated: Bounded { } /// The automatic implementation for any [`Intersected`] object to be used in the [`BVH`]. +/// +/// [`BVH`]: struct.BVH.html impl Accelerated for T where T: Intersected, From 5ebad7c1ab7a725d5407fc01ba45518aa8ab12f1 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 20:41:19 +0200 Subject: [PATCH 31/67] pathtracer: move rendering logic to 'render' module --- pathtracer/src/lib.rs | 1 + pathtracer/src/main.rs | 3 +- pathtracer/src/render/mod.rs | 6 + pathtracer/src/render/raytracer.rs | 249 ++++++++++++++++++++++ pathtracer/src/{scene => render}/utils.rs | 0 pathtracer/src/scene/mod.rs | 4 +- pathtracer/src/scene/scene.rs | 245 ++------------------- 7 files changed, 271 insertions(+), 237 deletions(-) create mode 100644 pathtracer/src/render/mod.rs create mode 100644 pathtracer/src/render/raytracer.rs rename pathtracer/src/{scene => render}/utils.rs (100%) diff --git a/pathtracer/src/lib.rs b/pathtracer/src/lib.rs index 200dd50..2dcd3a1 100644 --- a/pathtracer/src/lib.rs +++ b/pathtracer/src/lib.rs @@ -11,6 +11,7 @@ pub type Point2D = nalgebra::Point2; pub mod core; pub mod light; pub mod material; +pub mod render; pub mod scene; pub mod serialize; pub mod shape; diff --git a/pathtracer/src/main.rs b/pathtracer/src/main.rs index 3425e73..f8601a7 100644 --- a/pathtracer/src/main.rs +++ b/pathtracer/src/main.rs @@ -1,3 +1,4 @@ +use pathtracer::render::Raytracer; use pathtracer::scene::Scene; use std::path::PathBuf; use structopt::StructOpt; @@ -17,7 +18,7 @@ fn main() -> Result<(), Box> { let f = std::fs::File::open(options.input)?; let scene: Scene = serde_yaml::from_reader(f)?; - let image = scene.render(); + let image = Raytracer::new(scene).render(); image.save(options.output)?; Ok(()) diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/render/mod.rs new file mode 100644 index 0000000..dd37450 --- /dev/null +++ b/pathtracer/src/render/mod.rs @@ -0,0 +1,6 @@ +//! Define the different kinds of renderers for use on a given scene. + +mod raytracer; +pub use raytracer::*; + +pub(crate) mod utils; diff --git a/pathtracer/src/render/raytracer.rs b/pathtracer/src/render/raytracer.rs new file mode 100644 index 0000000..f231189 --- /dev/null +++ b/pathtracer/src/render/raytracer.rs @@ -0,0 +1,249 @@ +use super::utils::*; +use crate::scene::{Object, Scene}; +use crate::{ + core::{LightProperties, LinearColor, ReflTransEnum}, + material::Material, + shape::Shape, + texture::Texture, + {Point, Vector}, +}; +use beevee::ray::Ray; +use image::RgbImage; +use nalgebra::Unit; +use rand::prelude::thread_rng; +use rand::Rng; + +/// Render the [`Scene`] using Raytracing. +/// +/// [`Scene`]: ../scene/scene/struct.Scene.html +pub struct Raytracer { + scene: Scene, +} + +impl Raytracer { + /// Create a [`Raytracer`] renderer with the given [`Scene`] + /// + /// [`Raytracer`]: struct.Raytracer.html + /// [`Scene`]: ../scene/scene/struct.Scene.html + pub fn new(scene: Scene) -> Self { + Raytracer { scene } + } + + /// Render the [`Scene`] using Raytracing. + /// + /// [`Scene`]: ../scene/scene/struct.Scene.html + pub fn render(&self) -> RgbImage { + let mut image = RgbImage::new( + self.scene.camera.film().width(), + self.scene.camera.film().height(), + ); + + let total = (image.width() * image.height()) as u64; + let pb = indicatif::ProgressBar::new(total); + pb.set_draw_delta(total / 10000); + pb.set_style(indicatif::ProgressStyle::default_bar().template( + "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", + )); + + let pixel_func = if self.scene.aliasing_limit > 0 { + Self::anti_alias_pixel + } else { + Self::pixel + }; + + rayon::scope(|s| { + // FIXME(Bruno): it would go even faster to cut the image in blocks of rows, leading to + // better cache-line behaviour... + for (_, row) in image.enumerate_rows_mut() { + s.spawn(|_| { + for (x, y, pixel) in row { + *pixel = pixel_func(&self, x as f32, y as f32).into(); + pb.inc(1); + } + }) + } + }); + + pb.finish(); + image + } + + /// Get pixel color for (x, y) a pixel **coordinate** + fn pixel(&self, x: f32, y: f32) -> LinearColor { + let (x, y) = self.scene.camera.film().pixel_ratio(x, y); + let indices = RefractionInfo::with_index(self.scene.diffraction_index); + let ray = self.scene.camera.ray_with_ratio(x, y); + self.cast_ray(ray).map_or_else( + || self.scene.background.clone(), + |(t, obj)| { + self.color_at( + ray.origin + ray.direction.as_ref() * t, + obj, + ray.direction, + self.scene.reflection_limit, + indices, + ) + }, + ) + } + + /// Get pixel color with anti-aliasing + fn anti_alias_pixel(&self, x: f32, y: f32) -> LinearColor { + let range = 0..self.scene.aliasing_limit; + let mut rng = thread_rng(); + let acc: LinearColor = range + .map(|_| { + let random_x: f32 = rng.gen(); + let random_y: f32 = rng.gen(); + self.pixel(x + random_x, y + random_y) + }) + .map(LinearColor::clamp) + .sum(); + acc / self.scene.aliasing_limit as f32 + } + + fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { + self.scene.bvh.walk(&ray, &self.scene.objects) + } + + fn color_at( + &self, + point: Point, + object: &Object, + incident_ray: Unit, + reflection_limit: u32, + mut indices: RefractionInfo, + ) -> LinearColor { + let texel = object.shape.project_texel(&point); + let properties = object.material.properties(texel); + let object_color = object.texture.texel_color(texel); + + let normal = object.shape.normal(&point); + let reflected_ray = reflected(incident_ray, normal); + + // FIXME: change this to averaged sampled rays instead of visiting every light ? + // Indeed the path-tracing algorithm is good for calculating the radiance at a point + // But it should be used for reflection and refraction too... + let lighting = self.illuminate(point, object_color, &properties, normal, reflected_ray); + if properties.refl_trans.is_none() { + // Avoid calculating reflection when not needed + return lighting; + } + let reflected = self.reflection(point, reflected_ray, reflection_limit, indices.clone()); + // We can unwrap safely thanks to the check for None before + match properties.refl_trans.unwrap() { + ReflTransEnum::Transparency { coef, index } => { + // Calculate the refracted ray, if it was refracted, and mutate indices accordingly + refracted(incident_ray, normal, &mut indices, index).map_or_else( + // Total reflection + || reflected.clone(), + // Refraction (refracted ray, amount of *reflection*) + |(r, refl_t)| { + let refracted = self.refraction(point, coef, r, reflection_limit, indices); + let refr_light = refracted * (1. - refl_t) + reflected.clone() * refl_t; + refr_light * coef + lighting * (1. - coef) + }, + ) + } + ReflTransEnum::Reflectivity { coef } => reflected * coef + lighting * (1. - coef), + } + } + + fn refraction( + &self, + point: Point, + transparency: f32, + refracted: Unit, + reflection_limit: u32, + indices: RefractionInfo, + ) -> LinearColor { + if transparency > 1e-5 && reflection_limit > 0 { + let refraction_start = point + refracted.as_ref() * 0.001; + if let Some((t, obj)) = self.cast_ray(Ray::new(refraction_start, refracted)) { + let resulting_position = refraction_start + refracted.as_ref() * t; + let refracted = self.color_at( + resulting_position, + obj, + refracted, + reflection_limit - 1, + indices, + ); + return refracted * transparency; + } + } + LinearColor::black() + } + + fn reflection( + &self, + point: Point, + reflected: Unit, + reflection_limit: u32, + indices: RefractionInfo, + ) -> LinearColor { + if reflection_limit > 0 { + let reflection_start = point + reflected.as_ref() * 0.001; + if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) { + let resulting_position = reflection_start + reflected.as_ref() * t; + let color = self.color_at( + resulting_position, + obj, + reflected, + reflection_limit - 1, + indices, + ); + return color; + } + }; + LinearColor::black() + } + + fn illuminate( + &self, + point: Point, + object_color: LinearColor, + properties: &LightProperties, + normal: Unit, + reflected: Unit, + ) -> LinearColor { + let ambient = self.illuminate_ambient(object_color.clone()); + let spatial = self.illuminate_spatial(point, properties, normal, reflected); + ambient + object_color * spatial + } + + fn illuminate_ambient(&self, color: LinearColor) -> LinearColor { + self.scene + .lights + .ambient_lights_iter() + .map(|light| color.clone() * light.illumination(&Point::origin())) + .map(LinearColor::clamp) + .sum() + } + + fn illuminate_spatial( + &self, + point: Point, + properties: &LightProperties, + normal: Unit, + reflected: Unit, + ) -> LinearColor { + self.scene + .lights + .spatial_lights_iter() + .map(|light| { + let (direction, t) = light.to_source(&point); + let light_ray = Ray::new(point + direction.as_ref() * 0.001, direction); + match self.cast_ray(light_ray) { + // Take shadows into account + Some((obstacle_t, _)) if obstacle_t < t => return LinearColor::black(), + _ => {} + } + let lum = light.illumination(&point); + let diffused = properties.diffuse.clone() * normal.dot(&direction); + let specular = properties.specular.clone() * reflected.dot(&direction); + lum * (diffused + specular) + }) + .map(LinearColor::clamp) + .sum() + } +} diff --git a/pathtracer/src/scene/utils.rs b/pathtracer/src/render/utils.rs similarity index 100% rename from pathtracer/src/scene/utils.rs rename to pathtracer/src/render/utils.rs diff --git a/pathtracer/src/scene/mod.rs b/pathtracer/src/scene/mod.rs index 6d969e6..24d58d9 100644 --- a/pathtracer/src/scene/mod.rs +++ b/pathtracer/src/scene/mod.rs @@ -1,4 +1,4 @@ -//! Rendering logic +//! Desciption of the scene. pub mod light_aggregate; pub use light_aggregate::*; @@ -11,5 +11,3 @@ pub use object::*; pub mod scene; pub use scene::*; - -pub(crate) mod utils; diff --git a/pathtracer/src/scene/scene.rs b/pathtracer/src/scene/scene.rs index f0fb60b..db4ca83 100644 --- a/pathtracer/src/scene/scene.rs +++ b/pathtracer/src/scene/scene.rs @@ -1,32 +1,22 @@ -//! Scene rendering logic +//! Scene representation. -use super::{light_aggregate::LightAggregate, mesh::Mesh, object::Object, utils::*}; -use crate::{ - core::{Camera, LightProperties, LinearColor, ReflTransEnum}, - material::Material, - shape::Shape, - texture::Texture, - {Point, Vector}, -}; -use beevee::{bvh::BVH, ray::Ray}; -use image::RgbImage; -use nalgebra::Unit; -use rand::prelude::thread_rng; -use rand::Rng; +use super::{LightAggregate, Mesh, Object}; +use crate::core::{Camera, LinearColor}; +use beevee::bvh::BVH; use serde::Deserialize; /// Represent the scene being rendered. #[serde(from = "SerializedScene")] #[derive(Debug, PartialEq, Deserialize)] pub struct Scene { - camera: Camera, - lights: LightAggregate, - objects: Vec, - bvh: BVH, - background: LinearColor, - aliasing_limit: u32, - reflection_limit: u32, - diffraction_index: f32, + pub(crate) camera: Camera, + pub(crate) lights: LightAggregate, + pub(crate) objects: Vec, + pub(crate) bvh: BVH, + pub(crate) background: LinearColor, + pub(crate) aliasing_limit: u32, + pub(crate) reflection_limit: u32, + pub(crate) diffraction_index: f32, } impl Scene { @@ -85,217 +75,6 @@ impl Scene { diffraction_index, } } - - /// Render the scene into an image. - pub fn render(&self) -> RgbImage { - let mut image = RgbImage::new(self.camera.film().width(), self.camera.film().height()); - - let total = (image.width() * image.height()) as u64; - let pb = indicatif::ProgressBar::new(total); - pb.set_draw_delta(total / 10000); - pb.set_style(indicatif::ProgressStyle::default_bar().template( - "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", - )); - - let pixel_func = if self.aliasing_limit > 0 { - Self::anti_alias_pixel - } else { - Self::pixel - }; - - rayon::scope(|s| { - // FIXME(Bruno): it would go even faster to cut the image in blocks of rows, leading to - // better cache-line behaviour... - for (_, row) in image.enumerate_rows_mut() { - s.spawn(|_| { - for (x, y, pixel) in row { - *pixel = pixel_func(&self, x as f32, y as f32).into(); - pb.inc(1); - } - }) - } - }); - - pb.finish(); - image - } - - /// Get pixel color for (x, y) a pixel **coordinate** - fn pixel(&self, x: f32, y: f32) -> LinearColor { - let (x, y) = self.camera.film().pixel_ratio(x, y); - let indices = RefractionInfo::with_index(self.diffraction_index); - let ray = self.camera.ray_with_ratio(x, y); - self.cast_ray(ray).map_or_else( - || self.background.clone(), - |(t, obj)| { - self.color_at( - ray.origin + ray.direction.as_ref() * t, - obj, - ray.direction, - self.reflection_limit, - indices, - ) - }, - ) - } - - /// Get pixel color with anti-aliasing - fn anti_alias_pixel(&self, x: f32, y: f32) -> LinearColor { - let range = 0..self.aliasing_limit; - let mut rng = thread_rng(); - let acc: LinearColor = range - .map(|_| { - let random_x: f32 = rng.gen(); - let random_y: f32 = rng.gen(); - self.pixel(x + random_x, y + random_y) - }) - .map(LinearColor::clamp) - .sum(); - acc / self.aliasing_limit as f32 - } - - fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { - self.bvh.walk(&ray, &self.objects) - } - - fn color_at( - &self, - point: Point, - object: &Object, - incident_ray: Unit, - reflection_limit: u32, - mut indices: RefractionInfo, - ) -> LinearColor { - let texel = object.shape.project_texel(&point); - let properties = object.material.properties(texel); - let object_color = object.texture.texel_color(texel); - - let normal = object.shape.normal(&point); - let reflected_ray = reflected(incident_ray, normal); - - // FIXME: change this to averaged sampled rays instead of visiting every light ? - // Indeed the path-tracing algorithm is good for calculating the radiance at a point - // But it should be used for reflection and refraction too... - let lighting = self.illuminate(point, object_color, &properties, normal, reflected_ray); - if properties.refl_trans.is_none() { - // Avoid calculating reflection when not needed - return lighting; - } - let reflected = self.reflection(point, reflected_ray, reflection_limit, indices.clone()); - // We can unwrap safely thanks to the check for None before - match properties.refl_trans.unwrap() { - ReflTransEnum::Transparency { coef, index } => { - // Calculate the refracted ray, if it was refracted, and mutate indices accordingly - refracted(incident_ray, normal, &mut indices, index).map_or_else( - // Total reflection - || reflected.clone(), - // Refraction (refracted ray, amount of *reflection*) - |(r, refl_t)| { - let refracted = self.refraction(point, coef, r, reflection_limit, indices); - let refr_light = refracted * (1. - refl_t) + reflected.clone() * refl_t; - refr_light * coef + lighting * (1. - coef) - }, - ) - } - ReflTransEnum::Reflectivity { coef } => reflected * coef + lighting * (1. - coef), - } - } - - fn refraction( - &self, - point: Point, - transparency: f32, - refracted: Unit, - reflection_limit: u32, - indices: RefractionInfo, - ) -> LinearColor { - if transparency > 1e-5 && reflection_limit > 0 { - let refraction_start = point + refracted.as_ref() * 0.001; - if let Some((t, obj)) = self.cast_ray(Ray::new(refraction_start, refracted)) { - let resulting_position = refraction_start + refracted.as_ref() * t; - let refracted = self.color_at( - resulting_position, - obj, - refracted, - reflection_limit - 1, - indices, - ); - return refracted * transparency; - } - } - LinearColor::black() - } - - fn reflection( - &self, - point: Point, - reflected: Unit, - reflection_limit: u32, - indices: RefractionInfo, - ) -> LinearColor { - if reflection_limit > 0 { - let reflection_start = point + reflected.as_ref() * 0.001; - if let Some((t, obj)) = self.cast_ray(Ray::new(reflection_start, reflected)) { - let resulting_position = reflection_start + reflected.as_ref() * t; - let color = self.color_at( - resulting_position, - obj, - reflected, - reflection_limit - 1, - indices, - ); - return color; - } - }; - LinearColor::black() - } - - fn illuminate( - &self, - point: Point, - object_color: LinearColor, - properties: &LightProperties, - normal: Unit, - reflected: Unit, - ) -> LinearColor { - let ambient = self.illuminate_ambient(object_color.clone()); - let spatial = self.illuminate_spatial(point, properties, normal, reflected); - ambient + object_color * spatial - } - - fn illuminate_ambient(&self, color: LinearColor) -> LinearColor { - self.lights - .ambient_lights_iter() - .map(|light| color.clone() * light.illumination(&Point::origin())) - .map(LinearColor::clamp) - .sum() - } - - fn illuminate_spatial( - &self, - point: Point, - properties: &LightProperties, - normal: Unit, - reflected: Unit, - ) -> LinearColor { - self.lights - .spatial_lights_iter() - .map(|light| { - let (direction, t) = light.to_source(&point); - let light_ray = Ray::new(point + 0.001 * direction.as_ref(), direction); - match self.cast_ray(light_ray) { - // Take shadows into account - Some((obstacle_t, _)) if obstacle_t < t => return LinearColor::black(), - _ => {} - } - let lum = light.illumination(&point); - let diffused = properties.diffuse.clone() * normal.dot(&direction); - let specular = properties.specular.clone() * reflected.dot(&direction); - lum * (diffused + specular) - }) - .map(LinearColor::clamp) - .sum() - } } #[derive(Debug, PartialEq, Deserialize)] From 83ed6406ac5c2f2fcf55f6e2daa884777ebe2d17 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 21:04:24 +0200 Subject: [PATCH 32/67] executable: allow the choice of renderer --- pathtracer/src/main.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pathtracer/src/main.rs b/pathtracer/src/main.rs index f8601a7..aa10c72 100644 --- a/pathtracer/src/main.rs +++ b/pathtracer/src/main.rs @@ -1,8 +1,18 @@ use pathtracer::render::Raytracer; use pathtracer::scene::Scene; use std::path::PathBuf; +use std::str; +use structopt::clap::arg_enum; use structopt::StructOpt; +arg_enum! { + #[derive(Debug)] + enum RenderOption { + Raytracer, + Pathtracer, + } +} + #[derive(StructOpt, Debug)] struct Options { /// Input description for the scene to be rendered. @@ -11,6 +21,15 @@ struct Options { /// Output image for the rendered scene. #[structopt(short, long, parse(from_os_str), default_value = "scene.png")] output: PathBuf, + /// Which renderer should be used on the input scene. + #[structopt( + short, + long, + possible_values = &RenderOption::variants(), + case_insensitive = true, + default_value = "Raytracer" + )] + renderer: RenderOption, } fn main() -> Result<(), Box> { @@ -18,7 +37,10 @@ fn main() -> Result<(), Box> { let f = std::fs::File::open(options.input)?; let scene: Scene = serde_yaml::from_reader(f)?; - let image = Raytracer::new(scene).render(); + let image = match options.renderer { + RenderOption::Raytracer => Raytracer::new(scene).render(), + RenderOption::Pathtracer => todo!(), + }; image.save(options.output)?; Ok(()) From 9c6b9af31abf4e2c2688db72912f8fb1f637082a Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 29 Mar 2020 21:23:42 +0200 Subject: [PATCH 33/67] library: render: add Renderer trait --- pathtracer/src/render/mod.rs | 9 +++++++++ pathtracer/src/render/raytracer.rs | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/render/mod.rs index dd37450..89cde0c 100644 --- a/pathtracer/src/render/mod.rs +++ b/pathtracer/src/render/mod.rs @@ -1,4 +1,13 @@ //! Define the different kinds of renderers for use on a given scene. +use image::RgbImage; + +/// Each renderer implements this trait, to be called after being built. +pub trait Renderer { + /// Render the [`Scene`] using the chosen rendering technique. + /// + /// [`Scene`]: ../scene/scene/struct.Scene.html + fn render(&self) -> RgbImage; +} mod raytracer; pub use raytracer::*; diff --git a/pathtracer/src/render/raytracer.rs b/pathtracer/src/render/raytracer.rs index f231189..b6c564d 100644 --- a/pathtracer/src/render/raytracer.rs +++ b/pathtracer/src/render/raytracer.rs @@ -1,4 +1,5 @@ use super::utils::*; +use super::Renderer; use crate::scene::{Object, Scene}; use crate::{ core::{LightProperties, LinearColor, ReflTransEnum}, @@ -247,3 +248,9 @@ impl Raytracer { .sum() } } + +impl Renderer for Raytracer { + fn render(&self) -> RgbImage { + self.render() + } +} From b624ced37f89d498fa442aa381cf10ee86a347e8 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 30 Mar 2020 02:04:37 +0200 Subject: [PATCH 34/67] library: render: pathtrace: add dummy Pathtracer --- pathtracer/src/render/mod.rs | 3 ++ pathtracer/src/render/pathtrace/mod.rs | 2 ++ pathtracer/src/render/pathtrace/pathtracer.rs | 34 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 pathtracer/src/render/pathtrace/mod.rs create mode 100644 pathtracer/src/render/pathtrace/pathtracer.rs diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/render/mod.rs index 89cde0c..2da13a2 100644 --- a/pathtracer/src/render/mod.rs +++ b/pathtracer/src/render/mod.rs @@ -9,6 +9,9 @@ pub trait Renderer { fn render(&self) -> RgbImage; } +mod pathtrace; +pub use pathtrace::*; + mod raytracer; pub use raytracer::*; diff --git a/pathtracer/src/render/pathtrace/mod.rs b/pathtracer/src/render/pathtrace/mod.rs new file mode 100644 index 0000000..dd2cbdd --- /dev/null +++ b/pathtracer/src/render/pathtrace/mod.rs @@ -0,0 +1,2 @@ +mod pathtracer; +pub use self::pathtracer::*; diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs new file mode 100644 index 0000000..bba0c76 --- /dev/null +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -0,0 +1,34 @@ +use super::super::Renderer; +use crate::scene::Scene; +use image::RgbImage; + +/// Render the [`Scene`] using Bidirectional-Pathtracing +/// +/// [`Scene`]: ../scene/scene/struct.Scene.html +pub struct Pathtracer { + #[allow(unused)] + scene: Scene, +} + +impl Pathtracer { + /// Create a [`Pathtracer`] renderer with the given [`Scene`] + /// + /// [`Pathtracer`]: struct.Pathtracer.html + /// [`Scene`]: ../scene/scene/struct.Scene.html + pub fn new(scene: Scene) -> Self { + Pathtracer { scene } + } + + /// Render the [`Scene`] using Bidirectional-Pathtracing. + /// + /// [`Scene`]: ../scene/scene/struct.Scene.html + pub fn render(&self) -> RgbImage { + todo!() + } +} + +impl Renderer for Pathtracer { + fn render(&self) -> RgbImage { + self.render() + } +} From e68ceb484daf001878dc961e98d35bee133ffffd Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 30 Mar 2020 02:04:54 +0200 Subject: [PATCH 35/67] executable: use dummy pahtracer renderer --- pathtracer/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pathtracer/src/main.rs b/pathtracer/src/main.rs index aa10c72..8736550 100644 --- a/pathtracer/src/main.rs +++ b/pathtracer/src/main.rs @@ -1,4 +1,4 @@ -use pathtracer::render::Raytracer; +use pathtracer::render::{Pathtracer, Raytracer}; use pathtracer::scene::Scene; use std::path::PathBuf; use std::str; @@ -39,7 +39,7 @@ fn main() -> Result<(), Box> { let scene: Scene = serde_yaml::from_reader(f)?; let image = match options.renderer { RenderOption::Raytracer => Raytracer::new(scene).render(), - RenderOption::Pathtracer => todo!(), + RenderOption::Pathtracer => Pathtracer::new(scene).render(), }; image.save(options.output)?; From d4345e6ea4acea4bee9dadc187dabcc9435ab238 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Tue, 31 Mar 2020 12:08:15 +0200 Subject: [PATCH 36/67] library: scene: rename aliasing_limit to shot_rays --- pathtracer/src/render/raytracer.rs | 6 +++--- pathtracer/src/scene/scene.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pathtracer/src/render/raytracer.rs b/pathtracer/src/render/raytracer.rs index b6c564d..6757772 100644 --- a/pathtracer/src/render/raytracer.rs +++ b/pathtracer/src/render/raytracer.rs @@ -46,7 +46,7 @@ impl Raytracer { "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", )); - let pixel_func = if self.scene.aliasing_limit > 0 { + let pixel_func = if self.scene.shot_rays > 0 { Self::anti_alias_pixel } else { Self::pixel @@ -90,7 +90,7 @@ impl Raytracer { /// Get pixel color with anti-aliasing fn anti_alias_pixel(&self, x: f32, y: f32) -> LinearColor { - let range = 0..self.scene.aliasing_limit; + let range = 0..self.scene.shot_rays; let mut rng = thread_rng(); let acc: LinearColor = range .map(|_| { @@ -100,7 +100,7 @@ impl Raytracer { }) .map(LinearColor::clamp) .sum(); - acc / self.scene.aliasing_limit as f32 + acc / self.scene.shot_rays as f32 } fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { diff --git a/pathtracer/src/scene/scene.rs b/pathtracer/src/scene/scene.rs index db4ca83..6bf9386 100644 --- a/pathtracer/src/scene/scene.rs +++ b/pathtracer/src/scene/scene.rs @@ -14,7 +14,7 @@ pub struct Scene { pub(crate) objects: Vec, pub(crate) bvh: BVH, pub(crate) background: LinearColor, - pub(crate) aliasing_limit: u32, + pub(crate) shot_rays: u32, pub(crate) reflection_limit: u32, pub(crate) diffraction_index: f32, } @@ -49,7 +49,7 @@ impl Scene { /// ), /// ], /// LinearColor::black(), // Background color - /// 5, // aliasing limit + /// 5, // amount of rays shot per pixel /// 3, // reflection recursion limit /// 0.0, // diffraction index /// ); @@ -59,7 +59,7 @@ impl Scene { lights: LightAggregate, mut objects: Vec, background: LinearColor, - aliasing_limit: u32, + shot_rays: u32, reflection_limit: u32, diffraction_index: f32, ) -> Self { @@ -70,7 +70,7 @@ impl Scene { objects, bvh, background, - aliasing_limit, + shot_rays, reflection_limit, diffraction_index, } @@ -90,7 +90,7 @@ struct SerializedScene { #[serde(default)] background: LinearColor, #[serde(default)] - aliasing_limit: u32, + shot_rays: u32, #[serde(default)] reflection_limit: u32, #[serde(default = "crate::serialize::default_identity")] @@ -112,7 +112,7 @@ impl From for Scene { scene.lights, scene.objects, scene.background, - scene.aliasing_limit, + scene.shot_rays, scene.reflection_limit, scene.starting_diffraction, ) From a200a839b62de572ac3766fbe9f78a68f0e5fca9 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Tue, 31 Mar 2020 12:08:34 +0200 Subject: [PATCH 37/67] examples: rename aliasing_limit to shot_rays --- pathtracer/examples/colorful.yaml | 2 +- pathtracer/examples/scene.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pathtracer/examples/colorful.yaml b/pathtracer/examples/colorful.yaml index 6ed76fa..6dbdebd 100644 --- a/pathtracer/examples/colorful.yaml +++ b/pathtracer/examples/colorful.yaml @@ -1,5 +1,5 @@ # Optional field -aliasing_limit: 10 +shot_rays: 10 # Optional field reflection_limit: 5 diff --git a/pathtracer/examples/scene.yaml b/pathtracer/examples/scene.yaml index 90e79d3..d458181 100644 --- a/pathtracer/examples/scene.yaml +++ b/pathtracer/examples/scene.yaml @@ -1,4 +1,4 @@ -aliasing_limit: 10 +shot_rays: 10 reflection_limit: 5 background: {r: 0.5, g: 0.5, b: 0.5} From 6d0de72e57a89a4b8fb6e23b79eea6313a331281 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 30 Mar 2020 02:22:26 +0200 Subject: [PATCH 38/67] WIP: library: render: pathtrace: add Path struct --- pathtracer/src/render/pathtrace/mod.rs | 2 ++ pathtracer/src/render/pathtrace/path.rs | 44 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 pathtracer/src/render/pathtrace/path.rs diff --git a/pathtracer/src/render/pathtrace/mod.rs b/pathtracer/src/render/pathtrace/mod.rs index dd2cbdd..5f3e759 100644 --- a/pathtracer/src/render/pathtrace/mod.rs +++ b/pathtracer/src/render/pathtrace/mod.rs @@ -1,2 +1,4 @@ +mod path; + mod pathtracer; pub use self::pathtracer::*; diff --git a/pathtracer/src/render/pathtrace/path.rs b/pathtracer/src/render/pathtrace/path.rs new file mode 100644 index 0000000..28da0d4 --- /dev/null +++ b/pathtracer/src/render/pathtrace/path.rs @@ -0,0 +1,44 @@ +use crate::core::LightProperties; +use crate::{Point, Vector}; +use nalgebra::Unit; + +pub struct PathPoint { + pub point: Point, + pub incident: Unit, + pub normal: Unit, + pub properties: LightProperties, +} + +impl PathPoint { + pub fn new( + point: Point, + incident: Unit, + normal: Unit, + properties: LightProperties, + ) -> Self { + PathPoint { + point, + incident, + normal, + properties, + } + } +} + +pub struct Path { + pub origin: Point, + pub points: Vec, +} + +impl Path { + pub fn new(origin: Point) -> Self { + Path { + origin, + points: Vec::new(), + } + } + + pub fn push_point(&mut self, new_point: PathPoint) { + self.points.push(new_point) + } +} From 7eefd7b57477075c4e8bb7caadef6142f7376e6a Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 30 Mar 2020 02:23:12 +0200 Subject: [PATCH 39/67] WIP: library: render: pathtracer: add build_path --- pathtracer/src/render/pathtrace/pathtracer.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index bba0c76..6d113ab 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -1,6 +1,9 @@ use super::super::Renderer; +use super::path::*; use crate::scene::Scene; +use crate::{Point, Vector}; use image::RgbImage; +use nalgebra::Unit; /// Render the [`Scene`] using Bidirectional-Pathtracing /// @@ -25,6 +28,18 @@ impl Pathtracer { pub fn render(&self) -> RgbImage { todo!() } + + fn construct_path(&self, point: Point, direction: Unit) -> Path { + let mut res = Path::new(point); + for _ in 0..self.scene.reflection_limit { + // FIXME: + // * cast_ray: if no intersection, return the empty path + // * look-up information at intersection + // * append to path + // * start again with new origin + } + res + } } impl Renderer for Pathtracer { From 96995e7ef1992c7a26ab1124f0b57387eeb65488 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Wed, 1 Apr 2020 23:43:01 +0200 Subject: [PATCH 40/67] library: core: light_properties: add emitted light --- pathtracer/src/core/light_properties.rs | 44 ++++++++++++++++++++++--- pathtracer/src/material/triangle.rs | 18 +++++----- pathtracer/src/material/uniform.rs | 6 +++- pathtracer/src/scene/mesh.rs | 3 ++ pathtracer/src/scene/object.rs | 3 ++ pathtracer/src/scene/scene.rs | 1 + 6 files changed, 61 insertions(+), 14 deletions(-) diff --git a/pathtracer/src/core/light_properties.rs b/pathtracer/src/core/light_properties.rs index 9cc6bcc..6a75319 100644 --- a/pathtracer/src/core/light_properties.rs +++ b/pathtracer/src/core/light_properties.rs @@ -33,6 +33,9 @@ pub struct LightProperties { /// The transparency or reflectivity properties. #[serde(flatten)] pub refl_trans: Option, + /// The emitted light from this object, only used for path-tracing rendering techniques + #[serde(default)] + pub emitted: LinearColor, } impl LightProperties { @@ -48,17 +51,20 @@ impl LightProperties { /// LinearColor::new(0.25, 0.5, 1.), /// LinearColor::new(0.75, 0.375, 0.125), /// Some(ReflTransEnum::Reflectivity { coef: 0.5 }), + /// LinearColor::new(0., 0., 0.), /// ); /// ``` pub fn new( diffuse: LinearColor, specular: LinearColor, refl_trans: Option, + emitted: LinearColor, ) -> Self { LightProperties { diffuse, specular, refl_trans, + emitted, } } } @@ -72,14 +78,20 @@ mod test { let diffuse = LinearColor::new(0.25, 0.5, 1.); let specular = LinearColor::new(0.75, 0.375, 0.125); let refl_trans = Some(ReflTransEnum::Reflectivity { coef: 0.5 }); - let properties = - LightProperties::new(diffuse.clone(), specular.clone(), refl_trans.clone()); + let emitted = LinearColor::new(0., 1., 0.); + let properties = LightProperties::new( + diffuse.clone(), + specular.clone(), + refl_trans.clone(), + emitted.clone(), + ); assert_eq!( properties, LightProperties { diffuse, specular, refl_trans, + emitted, } ) } @@ -96,7 +108,8 @@ mod test { LightProperties::new( LinearColor::new(1., 0.5, 0.25), LinearColor::new(0.25, 0.125, 0.75), - None + None, + LinearColor::black(), ) ) } @@ -118,7 +131,8 @@ mod test { Some(ReflTransEnum::Transparency { coef: 0.5, index: 1.5 - }) + }), + LinearColor::black(), ) ) } @@ -136,7 +150,27 @@ mod test { LightProperties::new( LinearColor::new(1., 0.5, 0.25), LinearColor::new(0.25, 0.125, 0.75), - Some(ReflTransEnum::Reflectivity { coef: 0.25 }) + Some(ReflTransEnum::Reflectivity { coef: 0.25 }), + LinearColor::black(), + ) + ) + } + + #[test] + fn deserialization_with_emitted_works() { + let yaml = r#" + diffuse: {r: 1.0, g: 0.5, b: 0.25} + specular: {r: 0.25, g: 0.125, b: 0.75} + emitted: {r: 0.25, g: 0.5, b: 1.0} + "#; + let properties: LightProperties = serde_yaml::from_str(yaml).unwrap(); + assert_eq!( + properties, + LightProperties::new( + LinearColor::new(1., 0.5, 0.25), + LinearColor::new(0.25, 0.125, 0.75), + None, + LinearColor::new(0.25, 0.5, 1.0), ) ) } diff --git a/pathtracer/src/material/triangle.rs b/pathtracer/src/material/triangle.rs index 72790f2..66a0b72 100644 --- a/pathtracer/src/material/triangle.rs +++ b/pathtracer/src/material/triangle.rs @@ -12,19 +12,21 @@ pub struct TriangleMaterial { specular: [LinearColor; 3], /// The transparency or reflectivity properties, this is not interpolated. #[serde(flatten)] - pub refl_trans: Option, + refl_trans: Option, + /// The amount of light emitted by the material, only used during path-tracing rendering. + emitted: [LinearColor; 3], } impl Material for TriangleMaterial { fn properties(&self, point: Point2D) -> LightProperties { let (u, v) = (point.x, point.y); - let diffuse = self.diffuse[0].clone() * (1. - u - v) - + self.diffuse[1].clone() * u - + self.diffuse[2].clone() * v; - let specular = self.specular[0].clone() * (1. - u - v) - + self.specular[1].clone() * u - + self.specular[2].clone() * v; - LightProperties::new(diffuse, specular, self.refl_trans.clone()) + let sample = |param: &[LinearColor; 3]| -> LinearColor { + param[0].clone() * (1. - u - v) + param[1].clone() * u + param[2].clone() * v + }; + let diffuse = sample(&self.diffuse); + let specular = sample(&self.specular); + let emitted = sample(&self.emitted); + LightProperties::new(diffuse, specular, self.refl_trans.clone(), emitted) } } diff --git a/pathtracer/src/material/uniform.rs b/pathtracer/src/material/uniform.rs index 93fff05..030dd36 100644 --- a/pathtracer/src/material/uniform.rs +++ b/pathtracer/src/material/uniform.rs @@ -24,6 +24,7 @@ impl UniformMaterial { /// LinearColor::new(1.0, 0.0, 0.0), // diffuse component /// LinearColor::new(0.0, 0.0, 0.0), // specular component /// None, + /// LinearColor::black(), // Emitted light /// ), /// ); /// ``` @@ -50,6 +51,7 @@ mod test { diffuse: LinearColor::new(0., 0.5, 0.), specular: LinearColor::new(1., 1., 1.), refl_trans: None, + emitted: LinearColor::black(), }; let mat = UniformMaterial::new(properties.clone()); assert_eq!(mat, UniformMaterial { properties }) @@ -61,6 +63,7 @@ mod test { LinearColor::new(0., 0.5, 0.), LinearColor::new(1., 1., 1.), None, + LinearColor::black(), ); let mat = UniformMaterial::new(properties.clone()); assert_eq!(mat.properties(Point2D::origin()), properties) @@ -79,7 +82,8 @@ mod test { UniformMaterial::new(LightProperties::new( LinearColor::new(1., 0.5, 0.25), LinearColor::new(0.25, 0.125, 0.75), - Some(ReflTransEnum::Reflectivity { coef: 0.25 }) + Some(ReflTransEnum::Reflectivity { coef: 0.25 }), + LinearColor::black(), )) ) } diff --git a/pathtracer/src/scene/mesh.rs b/pathtracer/src/scene/mesh.rs index 3080c9c..d04cb4c 100644 --- a/pathtracer/src/scene/mesh.rs +++ b/pathtracer/src/scene/mesh.rs @@ -104,6 +104,8 @@ impl TryFrom for Mesh { // FIXME: material.dissolve is supposed to be "the alpha term" // Needs translation to our ReflTransEnum None, + // FIXME: parse 'Ke' component for emitted light + LinearColor::black(), )); // we only handle uniform textures @@ -118,6 +120,7 @@ impl TryFrom for Mesh { LinearColor::new(0.5, 0.5, 0.5), LinearColor::new(0.1, 0.1, 0.1), None, + LinearColor::black(), )) .into(), UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), diff --git a/pathtracer/src/scene/object.rs b/pathtracer/src/scene/object.rs index 21720d9..19a2585 100644 --- a/pathtracer/src/scene/object.rs +++ b/pathtracer/src/scene/object.rs @@ -42,6 +42,7 @@ impl Object { /// LinearColor::new(1.0, 0.0, 0.0), // diffuse component /// LinearColor::new(0.0, 0.0, 0.0), // specular component /// None, + /// LinearColor::black(), // Emitted light /// ), /// ).into(), /// UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), @@ -87,6 +88,7 @@ mod test { LinearColor::new(0.5, 0.5, 0.5), LinearColor::new(1., 1., 1.), None, + LinearColor::black(), )); let texture = UniformTexture::new(LinearColor::new(0.25, 0.5, 1.)); Object::new(shape.into(), material.into(), texture.into()) @@ -99,6 +101,7 @@ mod test { LinearColor::new(0.5, 0.5, 0.5), LinearColor::new(1., 1., 1.), None, + LinearColor::black(), )); let texture = UniformTexture::new(LinearColor::new(0.25, 0.5, 1.)); assert_eq!( diff --git a/pathtracer/src/scene/scene.rs b/pathtracer/src/scene/scene.rs index 6bf9386..c4165d2 100644 --- a/pathtracer/src/scene/scene.rs +++ b/pathtracer/src/scene/scene.rs @@ -43,6 +43,7 @@ impl Scene { /// LinearColor::new(1.0, 0.0, 0.0), // diffuse component /// LinearColor::new(0.0, 0.0, 0.0), // specular component /// None, + /// LinearColor::black(), // Emitted light /// ), /// ).into(), /// UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), From fc2de38b1a94114784e7e5409ba47d948590945f Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Wed, 1 Apr 2020 23:58:27 +0200 Subject: [PATCH 41/67] library: render: split bidirectional pathtracer --- .../bidirectional/bidirectional_pathtracer.rs | 49 +++++++++++++++++++ pathtracer/src/render/bidirectional/mod.rs | 4 ++ .../{pathtrace => bidirectional}/path.rs | 0 pathtracer/src/render/mod.rs | 3 ++ pathtracer/src/render/pathtrace/mod.rs | 2 - pathtracer/src/render/pathtrace/pathtracer.rs | 15 ------ 6 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs create mode 100644 pathtracer/src/render/bidirectional/mod.rs rename pathtracer/src/render/{pathtrace => bidirectional}/path.rs (100%) diff --git a/pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs b/pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs new file mode 100644 index 0000000..880b97c --- /dev/null +++ b/pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs @@ -0,0 +1,49 @@ +use super::super::Renderer; +use super::path::*; +use crate::scene::Scene; +use crate::{Point, Vector}; +use image::RgbImage; +use nalgebra::Unit; + +/// Render the [`Scene`] using Bidirectional-Pathtracing +/// +/// [`Scene`]: ../scene/scene/struct.Scene.html +pub struct BidirectionalPathtracer { + #[allow(unused)] + scene: Scene, +} + +impl BidirectionalPathtracer { + /// Create a [`BidirectionalPathtracer`] renderer with the given [`Scene`] + /// + /// [`BidirectionalPathtracer`]: struct.BidirectionalPathtracer.html + /// [`Scene`]: ../scene/scene/struct.Scene.html + pub fn new(scene: Scene) -> Self { + BidirectionalPathtracer { scene } + } + + /// Render the [`Scene`] using Bidirectional-Pathtracing. + /// + /// [`Scene`]: ../scene/scene/struct.Scene.html + pub fn render(&self) -> RgbImage { + todo!() + } + + fn construct_path(&self, point: Point, direction: Unit) -> Path { + let mut res = Path::new(point); + for _ in 0..self.scene.reflection_limit { + // FIXME: + // * cast_ray: if no intersection, return the empty path + // * look-up information at intersection + // * append to path + // * start again with new origin + } + res + } +} + +impl Renderer for BidirectionalPathtracer { + fn render(&self) -> RgbImage { + self.render() + } +} diff --git a/pathtracer/src/render/bidirectional/mod.rs b/pathtracer/src/render/bidirectional/mod.rs new file mode 100644 index 0000000..656d62b --- /dev/null +++ b/pathtracer/src/render/bidirectional/mod.rs @@ -0,0 +1,4 @@ +mod path; + +mod bidirectional_pathtracer; +pub use bidirectional_pathtracer::*; diff --git a/pathtracer/src/render/pathtrace/path.rs b/pathtracer/src/render/bidirectional/path.rs similarity index 100% rename from pathtracer/src/render/pathtrace/path.rs rename to pathtracer/src/render/bidirectional/path.rs diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/render/mod.rs index 2da13a2..2e62f69 100644 --- a/pathtracer/src/render/mod.rs +++ b/pathtracer/src/render/mod.rs @@ -9,6 +9,9 @@ pub trait Renderer { fn render(&self) -> RgbImage; } +mod bidirectional; +pub use bidirectional::*; + mod pathtrace; pub use pathtrace::*; diff --git a/pathtracer/src/render/pathtrace/mod.rs b/pathtracer/src/render/pathtrace/mod.rs index 5f3e759..dd2cbdd 100644 --- a/pathtracer/src/render/pathtrace/mod.rs +++ b/pathtracer/src/render/pathtrace/mod.rs @@ -1,4 +1,2 @@ -mod path; - mod pathtracer; pub use self::pathtracer::*; diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index 6d113ab..bba0c76 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -1,9 +1,6 @@ use super::super::Renderer; -use super::path::*; use crate::scene::Scene; -use crate::{Point, Vector}; use image::RgbImage; -use nalgebra::Unit; /// Render the [`Scene`] using Bidirectional-Pathtracing /// @@ -28,18 +25,6 @@ impl Pathtracer { pub fn render(&self) -> RgbImage { todo!() } - - fn construct_path(&self, point: Point, direction: Unit) -> Path { - let mut res = Path::new(point); - for _ in 0..self.scene.reflection_limit { - // FIXME: - // * cast_ray: if no intersection, return the empty path - // * look-up information at intersection - // * append to path - // * start again with new origin - } - res - } } impl Renderer for Pathtracer { From c0fc8851592cfb84e9cb5cc1afebd4f25f113f68 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 2 Apr 2020 00:02:11 +0200 Subject: [PATCH 42/67] library: render: add #[allow(unused)] attributes --- .../src/render/bidirectional/bidirectional_pathtracer.rs | 3 ++- pathtracer/src/render/bidirectional/path.rs | 3 +++ pathtracer/src/render/pathtrace/pathtracer.rs | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs b/pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs index 880b97c..f2bff2a 100644 --- a/pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs +++ b/pathtracer/src/render/bidirectional/bidirectional_pathtracer.rs @@ -29,7 +29,8 @@ impl BidirectionalPathtracer { todo!() } - fn construct_path(&self, point: Point, direction: Unit) -> Path { + #[allow(unused)] + fn construct_path(&self, point: Point, _direction: Unit) -> Path { let mut res = Path::new(point); for _ in 0..self.scene.reflection_limit { // FIXME: diff --git a/pathtracer/src/render/bidirectional/path.rs b/pathtracer/src/render/bidirectional/path.rs index 28da0d4..0bca927 100644 --- a/pathtracer/src/render/bidirectional/path.rs +++ b/pathtracer/src/render/bidirectional/path.rs @@ -10,6 +10,7 @@ pub struct PathPoint { } impl PathPoint { + #[allow(unused)] pub fn new( point: Point, incident: Unit, @@ -31,6 +32,7 @@ pub struct Path { } impl Path { + #[allow(unused)] pub fn new(origin: Point) -> Self { Path { origin, @@ -38,6 +40,7 @@ impl Path { } } + #[allow(unused)] pub fn push_point(&mut self, new_point: PathPoint) { self.points.push(new_point) } diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index bba0c76..a73c2db 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -2,7 +2,7 @@ use super::super::Renderer; use crate::scene::Scene; use image::RgbImage; -/// Render the [`Scene`] using Bidirectional-Pathtracing +/// Render the [`Scene`] using Pathtracing /// /// [`Scene`]: ../scene/scene/struct.Scene.html pub struct Pathtracer { @@ -19,7 +19,7 @@ impl Pathtracer { Pathtracer { scene } } - /// Render the [`Scene`] using Bidirectional-Pathtracing. + /// Render the [`Scene`] using Pathtracing. /// /// [`Scene`]: ../scene/scene/struct.Scene.html pub fn render(&self) -> RgbImage { From 4bb20e2f30fc204e67d0dce82410e0ec9e949197 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 2 Apr 2020 00:03:43 +0200 Subject: [PATCH 43/67] executable: add bidirectional renderer option --- pathtracer/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pathtracer/src/main.rs b/pathtracer/src/main.rs index 8736550..823e002 100644 --- a/pathtracer/src/main.rs +++ b/pathtracer/src/main.rs @@ -1,4 +1,4 @@ -use pathtracer::render::{Pathtracer, Raytracer}; +use pathtracer::render::{BidirectionalPathtracer, Pathtracer, Raytracer}; use pathtracer::scene::Scene; use std::path::PathBuf; use std::str; @@ -10,6 +10,7 @@ arg_enum! { enum RenderOption { Raytracer, Pathtracer, + Bidirectional, } } @@ -40,6 +41,7 @@ fn main() -> Result<(), Box> { let image = match options.renderer { RenderOption::Raytracer => Raytracer::new(scene).render(), RenderOption::Pathtracer => Pathtracer::new(scene).render(), + RenderOption::Bidirectional => BidirectionalPathtracer::new(scene).render(), }; image.save(options.output)?; From 70a923cf26c313cfdd0b1d9aec76a6b6ef3fa171 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 2 Apr 2020 00:06:33 +0200 Subject: [PATCH 44/67] library: render: move raytracer to own directory --- pathtracer/src/render/mod.rs | 4 ++-- pathtracer/src/render/raytrace/mod.rs | 2 ++ pathtracer/src/render/{ => raytrace}/raytracer.rs | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 pathtracer/src/render/raytrace/mod.rs rename pathtracer/src/render/{ => raytrace}/raytracer.rs (99%) diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/render/mod.rs index 2e62f69..1e2b4b2 100644 --- a/pathtracer/src/render/mod.rs +++ b/pathtracer/src/render/mod.rs @@ -15,7 +15,7 @@ pub use bidirectional::*; mod pathtrace; pub use pathtrace::*; -mod raytracer; -pub use raytracer::*; +mod raytrace; +pub use raytrace::*; pub(crate) mod utils; diff --git a/pathtracer/src/render/raytrace/mod.rs b/pathtracer/src/render/raytrace/mod.rs new file mode 100644 index 0000000..0f3fcf1 --- /dev/null +++ b/pathtracer/src/render/raytrace/mod.rs @@ -0,0 +1,2 @@ +mod raytracer; +pub use self::raytracer::*; diff --git a/pathtracer/src/render/raytracer.rs b/pathtracer/src/render/raytrace/raytracer.rs similarity index 99% rename from pathtracer/src/render/raytracer.rs rename to pathtracer/src/render/raytrace/raytracer.rs index 6757772..19b50dc 100644 --- a/pathtracer/src/render/raytracer.rs +++ b/pathtracer/src/render/raytrace/raytracer.rs @@ -1,5 +1,5 @@ -use super::utils::*; -use super::Renderer; +use super::super::utils::*; +use super::super::Renderer; use crate::scene::{Object, Scene}; use crate::{ core::{LightProperties, LinearColor, ReflTransEnum}, From 6066fe00dce0be1aea66f77128abd003a123e53f Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Thu, 2 Apr 2020 00:13:43 +0200 Subject: [PATCH 45/67] library: render: move progressbar creation to module --- pathtracer/src/render/mod.rs | 1 + pathtracer/src/render/progress.rs | 10 ++++++++++ pathtracer/src/render/raytrace/raytracer.rs | 6 +----- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 pathtracer/src/render/progress.rs diff --git a/pathtracer/src/render/mod.rs b/pathtracer/src/render/mod.rs index 1e2b4b2..d502605 100644 --- a/pathtracer/src/render/mod.rs +++ b/pathtracer/src/render/mod.rs @@ -18,4 +18,5 @@ pub use pathtrace::*; mod raytrace; pub use raytrace::*; +pub(crate) mod progress; pub(crate) mod utils; diff --git a/pathtracer/src/render/progress.rs b/pathtracer/src/render/progress.rs new file mode 100644 index 0000000..30698ee --- /dev/null +++ b/pathtracer/src/render/progress.rs @@ -0,0 +1,10 @@ +use indicatif::ProgressBar; + +pub fn get_progressbar(total: u64) -> ProgressBar { + let pb = ProgressBar::new(total); + pb.set_draw_delta(total / 10000); + pb.set_style(indicatif::ProgressStyle::default_bar().template( + "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", + )); + pb +} diff --git a/pathtracer/src/render/raytrace/raytracer.rs b/pathtracer/src/render/raytrace/raytracer.rs index 19b50dc..8aceb29 100644 --- a/pathtracer/src/render/raytrace/raytracer.rs +++ b/pathtracer/src/render/raytrace/raytracer.rs @@ -40,11 +40,7 @@ impl Raytracer { ); let total = (image.width() * image.height()) as u64; - let pb = indicatif::ProgressBar::new(total); - pb.set_draw_delta(total / 10000); - pb.set_style(indicatif::ProgressStyle::default_bar().template( - "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", - )); + let pb = super::super::progress::get_progressbar(total); let pixel_func = if self.scene.shot_rays > 0 { Self::anti_alias_pixel From 482e6bea3f4c843bfe9ecb6d35b5d7e5168f6e6b Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 6 Apr 2020 14:57:08 +0200 Subject: [PATCH 46/67] library: render: add basics for Pathtracer This is simple ray-tracing, with a binary white-or-black color depending on whether we hit an object or not. --- pathtracer/src/render/pathtrace/pathtracer.rs | 48 ++++++++++++++++++- pathtracer/src/render/progress.rs | 11 ++++- pathtracer/src/render/utils.rs | 21 ++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index a73c2db..8c4a8b7 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -1,5 +1,8 @@ +use super::super::utils::{buffer_to_image, prepare_buffer}; use super::super::Renderer; -use crate::scene::Scene; +use crate::core::LinearColor; +use crate::scene::{Object, Scene}; +use beevee::ray::Ray; use image::RgbImage; /// Render the [`Scene`] using Pathtracing @@ -23,7 +26,48 @@ impl Pathtracer { /// /// [`Scene`]: ../scene/scene/struct.Scene.html pub fn render(&self) -> RgbImage { - todo!() + let (width, height) = ( + self.scene.camera.film().width(), + self.scene.camera.film().height(), + ); + let total = width * height; + let mut buffer = prepare_buffer(total); + + // (total passes, film) + // FIXME: use MultiProgress because of rendering issues + let (pa, pb) = super::super::progress::get_multiple_progress(total, self.scene.shot_rays); + + // Ensure at least one round of shots + for _ in 0..self.scene.shot_rays.max(1) { + pb.reset(); // We're rendering the whole film again, reset the pixel counter + for y in 0..self.scene.camera.film().height() { + for x in 0..self.scene.camera.film().width() { + let i = x + y * self.scene.camera.film().width(); + buffer[i as usize] += self.pixel_ray(x as f32, y as f32); + pb.inc(1); + } + } + pa.inc(1); // Increment the number of passes + } + + pa.finish(); + pb.finish_and_clear(); + + buffer_to_image(buffer, self.scene.shot_rays, width, height) + } + + fn pixel_ray(&self, x: f32, y: f32) -> LinearColor { + let (x, y) = self.scene.camera.film().pixel_ratio(x, y); + let ray = self.scene.camera.ray_with_ratio(x, y); + if let Some(_) = self.cast_ray(ray) { + LinearColor::new(1., 1., 1.) // FIXME: calculate real color + } else { + LinearColor::black() + } + } + + fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { + self.scene.bvh.walk(&ray, &self.scene.objects) } } diff --git a/pathtracer/src/render/progress.rs b/pathtracer/src/render/progress.rs index 30698ee..15d5851 100644 --- a/pathtracer/src/render/progress.rs +++ b/pathtracer/src/render/progress.rs @@ -2,9 +2,18 @@ use indicatif::ProgressBar; pub fn get_progressbar(total: u64) -> ProgressBar { let pb = ProgressBar::new(total); - pb.set_draw_delta(total / 10000); + pb.set_draw_delta((total / 10000).max(1)); pb.set_style(indicatif::ProgressStyle::default_bar().template( "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", )); pb } + +pub fn get_multiple_progress(total: u32, passes: u32) -> (ProgressBar, ProgressBar) { + let pb = ProgressBar::new(passes as u64); + pb.set_draw_delta((passes as u64 / 100).max(1)); + pb.set_style(indicatif::ProgressStyle::default_bar().template( + "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} passes (ETA: {eta})", + )); + (pb, get_progressbar(total as u64)) +} diff --git a/pathtracer/src/render/utils.rs b/pathtracer/src/render/utils.rs index 2ff48ce..905f117 100644 --- a/pathtracer/src/render/utils.rs +++ b/pathtracer/src/render/utils.rs @@ -1,4 +1,6 @@ +use crate::core::LinearColor; use crate::Vector; +use image::RgbImage; use nalgebra::Unit; use rand::prelude::thread_rng; use rand::Rng; @@ -103,6 +105,25 @@ pub fn sample_hemisphere(normal: Vector) -> (Vector, f32) { (scattered, 1. / scattered.dot(&normal)) } +pub fn prepare_buffer(total: u32) -> Vec { + let mut ans = Vec::with_capacity(total as usize); + for _ in 0..total { + ans.push(LinearColor::black()); + } + ans +} + +pub fn buffer_to_image(buffer: Vec, passes: u32, width: u32, height: u32) -> RgbImage { + let mut image = RgbImage::new(width, height); + + for (x, y, pixel) in image.enumerate_pixels_mut() { + let i = x as usize + y as usize * width as usize; + *pixel = (buffer[i].clone() / passes as f32).into(); + } + + image +} + #[cfg(test)] mod test { use super::*; From c1801a7a78193431f93ebeb37fa903bae25d6f1d Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 6 Apr 2020 15:07:43 +0200 Subject: [PATCH 47/67] library: render: pathtracer: use map_or_else --- pathtracer/src/render/pathtrace/pathtracer.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index 8c4a8b7..cc26265 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -59,11 +59,12 @@ impl Pathtracer { fn pixel_ray(&self, x: f32, y: f32) -> LinearColor { let (x, y) = self.scene.camera.film().pixel_ratio(x, y); let ray = self.scene.camera.ray_with_ratio(x, y); - if let Some(_) = self.cast_ray(ray) { - LinearColor::new(1., 1., 1.) // FIXME: calculate real color - } else { - LinearColor::black() - } + self.cast_ray(ray).map_or_else( + || self.scene.background.clone(), + |(t, obj)| { + LinearColor::new(1., 1., 1.) // FIXME: calculate real color + }, + ) } fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { From ddebd55fcdf17e68f16e51d30af58bece8a7c9d3 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 6 Apr 2020 15:23:41 +0200 Subject: [PATCH 48/67] library: render: utils: use Unit for vectors --- pathtracer/src/render/utils.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pathtracer/src/render/utils.rs b/pathtracer/src/render/utils.rs index 905f117..4ed4c3a 100644 --- a/pathtracer/src/render/utils.rs +++ b/pathtracer/src/render/utils.rs @@ -72,8 +72,7 @@ impl RefractionInfo { /// Returns a random ray in the hemisphere described by a normal unit-vector, and the probability /// to have picked that direction. -#[allow(unused)] // FIXME: remove once used -pub fn sample_hemisphere(normal: Vector) -> (Vector, f32) { +pub fn sample_hemisphere(normal: Unit) -> (Unit, f32) { let mut rng = thread_rng(); let azimuth = rng.gen::() * std::f32::consts::PI * 2.; // Cosine weighted importance sampling @@ -94,11 +93,11 @@ pub fn sample_hemisphere(normal: Vector) -> (Vector, f32) { let normal_b = normal.cross(&normal_t); // Perform the matrix calculation by hand... - let scattered = Vector::new( + let scattered = Unit::new_normalize(Vector::new( x * normal_b.x + y * normal.x + z * normal_t.x, x * normal_b.y + y * normal.y + z * normal_t.y, x * normal_b.z + y * normal.z + z * normal_t.z, - ); + )); // The probability to have picked the ray is inversely proportional to cosine of the angle with // the normal @@ -133,7 +132,7 @@ mod test { // NOTE(Bruno): should use some test-case generation for failure-reproduction purposes... let mut rng = thread_rng(); for _ in 0..100 { - let normal = Vector::new(rng.gen(), rng.gen(), rng.gen()); + let normal = Unit::new_normalize(Vector::new(rng.gen(), rng.gen(), rng.gen())); for _ in 0..100 { let (sample, proportion) = sample_hemisphere(normal); let cos_angle = normal.dot(&sample); From a10fd07f43be4d54279915cba6639670c102c369 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Mon, 6 Apr 2020 15:40:34 +0200 Subject: [PATCH 49/67] library: render: pathtracer: basic pathtracing --- pathtracer/src/render/pathtrace/pathtracer.rs | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index cc26265..a4363be 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -1,7 +1,11 @@ -use super::super::utils::{buffer_to_image, prepare_buffer}; +use super::super::utils::{buffer_to_image, prepare_buffer, sample_hemisphere}; use super::super::Renderer; -use crate::core::LinearColor; -use crate::scene::{Object, Scene}; +use crate::{ + core::LinearColor, + material::Material, + scene::{Object, Scene}, + shape::Shape, +}; use beevee::ray::Ray; use image::RgbImage; @@ -61,12 +65,38 @@ impl Pathtracer { let ray = self.scene.camera.ray_with_ratio(x, y); self.cast_ray(ray).map_or_else( || self.scene.background.clone(), - |(t, obj)| { - LinearColor::new(1., 1., 1.) // FIXME: calculate real color - }, + |(t, obj)| self.radiance(ray, t, obj, self.scene.reflection_limit), ) } + fn radiance(&self, ray: Ray, t: f32, obj: &Object, limit: u32) -> LinearColor { + // This doesn't look great, but it works ¯\_(ツ)_/¯ + + let hit_pos = ray.origin + ray.direction.as_ref() * t; + let texel = obj.shape.project_texel(&hit_pos); + let properties = obj.material.properties(texel); + // If we are the at recursion limit, return the light emitted by the object + if limit == 0 { + return properties.emitted; + }; + // Get BRDF + // FIXME: what about the material's albedo ? + let brdf = properties.diffuse; + // Pick a new direction + let normal = obj.shape.normal(&hit_pos); + let (new_direction, weight) = sample_hemisphere(normal); + let cos_new_ray = new_direction.dot(&normal); + // Calculate the incoming light along the new ray + let new_ray = Ray::new(hit_pos + new_direction.as_ref() * 0.001, new_direction); + let incoming = self + .cast_ray(new_ray) + .map_or_else(LinearColor::black, |(t, obj)| { + self.radiance(new_ray, t, obj, limit - 1) + }); + // Put it all together + properties.emitted + (brdf * incoming * cos_new_ray * weight) + } + fn cast_ray(&self, ray: Ray) -> Option<(f32, &Object)> { self.scene.bvh.walk(&ray, &self.scene.objects) } From 0c289ca482301230f5fb6bd3e2dc47f5bc6ccda7 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 7 Apr 2020 17:31:19 +0200 Subject: [PATCH 50/67] library: scene: mesh: parse emitted light from MTL --- pathtracer/Cargo.toml | 5 ++++- pathtracer/src/scene/mesh.rs | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pathtracer/Cargo.toml b/pathtracer/Cargo.toml index f80ec3b..2673431 100644 --- a/pathtracer/Cargo.toml +++ b/pathtracer/Cargo.toml @@ -28,7 +28,6 @@ rand = "0.7" rayon = "1.3.0" serde_yaml = "0.8" structopt = "0.3" -tobj = "1.0" [dependencies.nalgebra] version = "0.20.0" @@ -37,3 +36,7 @@ features = ["serde-serialize"] [dependencies.serde] version = "1.0" features = ["derive"] + +[dependencies.tobj] +git = "https://github.com/alarsyo/tobj" # forked for Ke support +rev = "82b5c2ca" diff --git a/pathtracer/src/scene/mesh.rs b/pathtracer/src/scene/mesh.rs index d04cb4c..974680c 100644 --- a/pathtracer/src/scene/mesh.rs +++ b/pathtracer/src/scene/mesh.rs @@ -97,6 +97,7 @@ impl TryFrom for Mesh { let diffuse = LinearColor::from_slice(&mesh_mat.ambient[..]); let specular = LinearColor::from_slice(&mesh_mat.ambient[..]); + let emitted = LinearColor::from_slice(&mesh_mat.emission[..]); let material = UniformMaterial::new(LightProperties::new( diffuse.clone(), @@ -104,8 +105,7 @@ impl TryFrom for Mesh { // FIXME: material.dissolve is supposed to be "the alpha term" // Needs translation to our ReflTransEnum None, - // FIXME: parse 'Ke' component for emitted light - LinearColor::black(), + emitted, )); // we only handle uniform textures From c0c34fba7df20afbfcf4c432614c3498df4e1c9e Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 7 Apr 2020 20:16:53 +0200 Subject: [PATCH 51/67] library: scene: mesh: use unknown_param for Ke tobj actually has an `unknown_param` map with everything that's not in the official MTL spec, so let's use that instead of forking tobj --- pathtracer/Cargo.toml | 5 +--- pathtracer/src/scene/mesh.rs | 48 +++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/pathtracer/Cargo.toml b/pathtracer/Cargo.toml index 2673431..f80ec3b 100644 --- a/pathtracer/Cargo.toml +++ b/pathtracer/Cargo.toml @@ -28,6 +28,7 @@ rand = "0.7" rayon = "1.3.0" serde_yaml = "0.8" structopt = "0.3" +tobj = "1.0" [dependencies.nalgebra] version = "0.20.0" @@ -36,7 +37,3 @@ features = ["serde-serialize"] [dependencies.serde] version = "1.0" features = ["derive"] - -[dependencies.tobj] -git = "https://github.com/alarsyo/tobj" # forked for Ke support -rev = "82b5c2ca" diff --git a/pathtracer/src/scene/mesh.rs b/pathtracer/src/scene/mesh.rs index 974680c..7895029 100644 --- a/pathtracer/src/scene/mesh.rs +++ b/pathtracer/src/scene/mesh.rs @@ -35,6 +35,26 @@ pub(crate) struct Wavefront { scale: f32, } +fn parse_float3(s: &str) -> Result<[f32; 3], tobj::LoadError> { + let mut res = [0.0, 0.0, 0.0]; + let mut count = 0; + + for (i, s) in s.split_whitespace().enumerate() { + if count == 3 { + return Err(tobj::LoadError::MaterialParseError); + } + + res[i] = s.parse().map_err(|_| tobj::LoadError::MaterialParseError)?; + count += 1; + } + + if count < 3 { + return Err(tobj::LoadError::MaterialParseError); + } + + Ok(res) +} + impl TryFrom for Mesh { type Error = tobj::LoadError; @@ -97,7 +117,15 @@ impl TryFrom for Mesh { let diffuse = LinearColor::from_slice(&mesh_mat.ambient[..]); let specular = LinearColor::from_slice(&mesh_mat.ambient[..]); - let emitted = LinearColor::from_slice(&mesh_mat.emission[..]); + let emitted = mesh_mat + .unknown_param + .get("Ke") + // we want a default if "Ke" isn't provided, but we + // want an error if it is provided but its value + // doesn't parse + .map_or(Ok(LinearColor::black()), |ke| { + parse_float3(ke).map(|vals| LinearColor::from_slice(&vals)) + })?; let material = UniformMaterial::new(LightProperties::new( diffuse.clone(), @@ -134,3 +162,21 @@ impl TryFrom for Mesh { Ok(Mesh { shapes }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_float3_works() { + assert_eq!(parse_float3("1 1 1"), Ok([1., 1., 1.])); + assert_eq!( + parse_float3("1 1"), + Err(tobj::LoadError::MaterialParseError) + ); + assert_eq!( + parse_float3("1 1 1 1"), + Err(tobj::LoadError::MaterialParseError) + ); + } +} From 3f2c7b40e85b7b3c00db35e34b2ac08b37cf33cf Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 7 Apr 2020 22:19:38 +0200 Subject: [PATCH 52/67] library: render: pathtracer: draft parallelize --- pathtracer/examples/cornell-box.yaml | 1 + pathtracer/src/render/pathtrace/pathtracer.rs | 53 ++++++++++++------- pathtracer/src/render/progress.rs | 9 ---- pathtracer/src/render/utils.rs | 8 --- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/pathtracer/examples/cornell-box.yaml b/pathtracer/examples/cornell-box.yaml index d6fa9bb..e6b0f46 100644 --- a/pathtracer/examples/cornell-box.yaml +++ b/pathtracer/examples/cornell-box.yaml @@ -1,4 +1,5 @@ reflection_limit: 5 +shot_rays: 50 camera: origin: [0.0, 1.0, 0.0] diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index a4363be..dec5700 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -1,4 +1,6 @@ -use super::super::utils::{buffer_to_image, prepare_buffer, sample_hemisphere}; +use rayon::prelude::*; + +use super::super::utils::{buffer_to_image, sample_hemisphere}; use super::super::Renderer; use crate::{ core::LinearColor, @@ -35,29 +37,44 @@ impl Pathtracer { self.scene.camera.film().height(), ); let total = width * height; - let mut buffer = prepare_buffer(total); - // (total passes, film) - // FIXME: use MultiProgress because of rendering issues - let (pa, pb) = super::super::progress::get_multiple_progress(total, self.scene.shot_rays); + let p = super::super::progress::get_progressbar(self.scene.shot_rays as u64); // Ensure at least one round of shots - for _ in 0..self.scene.shot_rays.max(1) { - pb.reset(); // We're rendering the whole film again, reset the pixel counter - for y in 0..self.scene.camera.film().height() { - for x in 0..self.scene.camera.film().width() { - let i = x + y * self.scene.camera.film().width(); - buffer[i as usize] += self.pixel_ray(x as f32, y as f32); - pb.inc(1); + let img_buf = (0..self.scene.shot_rays.max(1)) + .into_par_iter() + .map(|_| { + let mut buffer: Vec = Vec::new(); + buffer.resize_with(total as usize, LinearColor::black); + + for y in 0..self.scene.camera.film().height() { + for x in 0..self.scene.camera.film().width() { + let i = x + y * self.scene.camera.film().width(); + buffer[i as usize] += self.pixel_ray(x as f32, y as f32); + } } - } - pa.inc(1); // Increment the number of passes - } + p.inc(1); // Increment the number of passes - pa.finish(); - pb.finish_and_clear(); + buffer + }) + .reduce( + || { + let mut vec = Vec::new(); + vec.resize_with(total as usize, LinearColor::black); + vec + }, + |mut acc, buf| { + for (i, pixel) in buf.into_iter().enumerate() { + acc[i] += pixel; + } - buffer_to_image(buffer, self.scene.shot_rays, width, height) + acc + }, + ); + + p.finish(); + + buffer_to_image(img_buf, self.scene.shot_rays, width, height) } fn pixel_ray(&self, x: f32, y: f32) -> LinearColor { diff --git a/pathtracer/src/render/progress.rs b/pathtracer/src/render/progress.rs index 15d5851..4d2c398 100644 --- a/pathtracer/src/render/progress.rs +++ b/pathtracer/src/render/progress.rs @@ -8,12 +8,3 @@ pub fn get_progressbar(total: u64) -> ProgressBar { )); pb } - -pub fn get_multiple_progress(total: u32, passes: u32) -> (ProgressBar, ProgressBar) { - let pb = ProgressBar::new(passes as u64); - pb.set_draw_delta((passes as u64 / 100).max(1)); - pb.set_style(indicatif::ProgressStyle::default_bar().template( - "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} passes (ETA: {eta})", - )); - (pb, get_progressbar(total as u64)) -} diff --git a/pathtracer/src/render/utils.rs b/pathtracer/src/render/utils.rs index 4ed4c3a..086a31e 100644 --- a/pathtracer/src/render/utils.rs +++ b/pathtracer/src/render/utils.rs @@ -104,14 +104,6 @@ pub fn sample_hemisphere(normal: Unit) -> (Unit, f32) { (scattered, 1. / scattered.dot(&normal)) } -pub fn prepare_buffer(total: u32) -> Vec { - let mut ans = Vec::with_capacity(total as usize); - for _ in 0..total { - ans.push(LinearColor::black()); - } - ans -} - pub fn buffer_to_image(buffer: Vec, passes: u32, width: u32, height: u32) -> RgbImage { let mut image = RgbImage::new(width, height); From f7780cb54e122299be62757516ac608b17b8e09c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 7 Apr 2020 22:56:00 +0200 Subject: [PATCH 53/67] rustfmt: bump to latest version to please CI --- .rustfmt.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 9849776..a9721da 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -52,7 +52,7 @@ use_field_init_shorthand = false force_explicit_abi = true condense_wildcard_suffixes = false color = "Auto" -required_version = "1.4.12" +required_version = "1.4.13" unstable_features = false disable_all_formatting = false skip_children = false From b89e01107d18e952786ce7bcc11b7f14aaba46c3 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 7 Apr 2020 23:01:56 +0200 Subject: [PATCH 54/67] library: unnest scene module to please clippy warning: module has the same name as its containing module --> pathtracer/src/scene/mod.rs:12:1 | 12 | pub mod scene; | ^^^^^^^^^^^^^^ | = note: `#[warn(clippy::module_inception)]` on by default --- pathtracer/src/scene/mod.rs | 149 +++++++++++++++++++++++++++++++++- pathtracer/src/scene/scene.rs | 149 ---------------------------------- 2 files changed, 147 insertions(+), 151 deletions(-) delete mode 100644 pathtracer/src/scene/scene.rs diff --git a/pathtracer/src/scene/mod.rs b/pathtracer/src/scene/mod.rs index 24d58d9..7d4e0af 100644 --- a/pathtracer/src/scene/mod.rs +++ b/pathtracer/src/scene/mod.rs @@ -1,5 +1,10 @@ //! Desciption of the scene. +use beevee::bvh::BVH; +use serde::Deserialize; + +use crate::core::{Camera, LinearColor}; + pub mod light_aggregate; pub use light_aggregate::*; @@ -9,5 +14,145 @@ pub use mesh::*; pub mod object; pub use object::*; -pub mod scene; -pub use scene::*; +/// Represent the scene being rendered. +#[serde(from = "SerializedScene")] +#[derive(Debug, PartialEq, Deserialize)] +pub struct Scene { + pub(crate) camera: Camera, + pub(crate) lights: LightAggregate, + pub(crate) objects: Vec, + pub(crate) bvh: BVH, + pub(crate) background: LinearColor, + pub(crate) shot_rays: u32, + pub(crate) reflection_limit: u32, + pub(crate) diffraction_index: f32, +} + +impl Scene { + /// Creates a new `Scene`. + /// + /// # Examples + /// + /// ``` + /// # use pathtracer::core::{Camera, LightProperties, LinearColor}; + /// # use pathtracer::material::UniformMaterial; + /// # use pathtracer::scene::{LightAggregate, Object, Scene}; + /// # use pathtracer::shape::Sphere; + /// # use pathtracer::texture::UniformTexture; + /// # use pathtracer::Point; + /// # + /// let scene = Scene::new( + /// Camera::default(), + /// LightAggregate::empty(), + /// vec![ + /// Object::new( + /// Sphere::new(Point::origin(), 1.0).into(), + /// UniformMaterial::new( + /// LightProperties::new( + /// LinearColor::new(1.0, 0.0, 0.0), // diffuse component + /// LinearColor::new(0.0, 0.0, 0.0), // specular component + /// None, + /// LinearColor::black(), // Emitted light + /// ), + /// ).into(), + /// UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), + /// ), + /// ], + /// LinearColor::black(), // Background color + /// 5, // amount of rays shot per pixel + /// 3, // reflection recursion limit + /// 0.0, // diffraction index + /// ); + /// ``` + pub fn new( + camera: Camera, + lights: LightAggregate, + mut objects: Vec, + background: LinearColor, + shot_rays: u32, + reflection_limit: u32, + diffraction_index: f32, + ) -> Self { + let bvh = BVH::build(&mut objects); + Scene { + camera, + lights, + objects, + bvh, + background, + shot_rays, + reflection_limit, + diffraction_index, + } + } +} + +#[derive(Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +struct SerializedScene { + camera: Camera, + #[serde(default)] + lights: LightAggregate, + #[serde(default)] + objects: Vec, + #[serde(default)] + meshes: Vec, + #[serde(default)] + background: LinearColor, + #[serde(default)] + shot_rays: u32, + #[serde(default)] + reflection_limit: u32, + #[serde(default = "crate::serialize::default_identity")] + starting_diffraction: f32, +} + +impl From for Scene { + fn from(mut scene: SerializedScene) -> Self { + let mut flattened_meshes: Vec = scene + .meshes + .into_iter() + .map(|m| m.shapes) + .flatten() + .collect(); + scene.objects.append(&mut flattened_meshes); + + Scene::new( + scene.camera, + scene.lights, + scene.objects, + scene.background, + scene.shot_rays, + scene.reflection_limit, + scene.starting_diffraction, + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn deserialization_works() { + let yaml = std::include_str!("../../examples/scene.yaml"); + let _: Scene = serde_yaml::from_str(yaml).unwrap(); + // FIXME: actually test the equality ? + } + + #[test] + fn empty_scene() { + use crate::core::Camera; + use crate::scene::{LightAggregate, Scene}; + + let _scene = Scene::new( + Camera::default(), + LightAggregate::empty(), + Vec::new(), // Objects list + LinearColor::black(), // Background color + 5, // aliasing limit + 3, // reflection recursion limit + 0.0, // diffraction index + ); + } +} diff --git a/pathtracer/src/scene/scene.rs b/pathtracer/src/scene/scene.rs deleted file mode 100644 index c4165d2..0000000 --- a/pathtracer/src/scene/scene.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! Scene representation. - -use super::{LightAggregate, Mesh, Object}; -use crate::core::{Camera, LinearColor}; -use beevee::bvh::BVH; -use serde::Deserialize; - -/// Represent the scene being rendered. -#[serde(from = "SerializedScene")] -#[derive(Debug, PartialEq, Deserialize)] -pub struct Scene { - pub(crate) camera: Camera, - pub(crate) lights: LightAggregate, - pub(crate) objects: Vec, - pub(crate) bvh: BVH, - pub(crate) background: LinearColor, - pub(crate) shot_rays: u32, - pub(crate) reflection_limit: u32, - pub(crate) diffraction_index: f32, -} - -impl Scene { - /// Creates a new `Scene`. - /// - /// # Examples - /// - /// ``` - /// # use pathtracer::core::{Camera, LightProperties, LinearColor}; - /// # use pathtracer::material::UniformMaterial; - /// # use pathtracer::scene::{LightAggregate, Object, Scene}; - /// # use pathtracer::shape::Sphere; - /// # use pathtracer::texture::UniformTexture; - /// # use pathtracer::Point; - /// # - /// let scene = Scene::new( - /// Camera::default(), - /// LightAggregate::empty(), - /// vec![ - /// Object::new( - /// Sphere::new(Point::origin(), 1.0).into(), - /// UniformMaterial::new( - /// LightProperties::new( - /// LinearColor::new(1.0, 0.0, 0.0), // diffuse component - /// LinearColor::new(0.0, 0.0, 0.0), // specular component - /// None, - /// LinearColor::black(), // Emitted light - /// ), - /// ).into(), - /// UniformTexture::new(LinearColor::new(0.5, 0.5, 0.5)).into(), - /// ), - /// ], - /// LinearColor::black(), // Background color - /// 5, // amount of rays shot per pixel - /// 3, // reflection recursion limit - /// 0.0, // diffraction index - /// ); - /// ``` - pub fn new( - camera: Camera, - lights: LightAggregate, - mut objects: Vec, - background: LinearColor, - shot_rays: u32, - reflection_limit: u32, - diffraction_index: f32, - ) -> Self { - let bvh = BVH::build(&mut objects); - Scene { - camera, - lights, - objects, - bvh, - background, - shot_rays, - reflection_limit, - diffraction_index, - } - } -} - -#[derive(Debug, PartialEq, Deserialize)] -#[serde(deny_unknown_fields)] -struct SerializedScene { - camera: Camera, - #[serde(default)] - lights: LightAggregate, - #[serde(default)] - objects: Vec, - #[serde(default)] - meshes: Vec, - #[serde(default)] - background: LinearColor, - #[serde(default)] - shot_rays: u32, - #[serde(default)] - reflection_limit: u32, - #[serde(default = "crate::serialize::default_identity")] - starting_diffraction: f32, -} - -impl From for Scene { - fn from(mut scene: SerializedScene) -> Self { - let mut flattened_meshes: Vec = scene - .meshes - .into_iter() - .map(|m| m.shapes) - .flatten() - .collect(); - scene.objects.append(&mut flattened_meshes); - - Scene::new( - scene.camera, - scene.lights, - scene.objects, - scene.background, - scene.shot_rays, - scene.reflection_limit, - scene.starting_diffraction, - ) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn deserialization_works() { - let yaml = std::include_str!("../../examples/scene.yaml"); - let _: Scene = serde_yaml::from_str(yaml).unwrap(); - // FIXME: actually test the equality ? - } - - #[test] - fn empty_scene() { - use crate::core::Camera; - use crate::scene::{LightAggregate, Scene}; - - let _scene = Scene::new( - Camera::default(), - LightAggregate::empty(), - Vec::new(), // Objects list - LinearColor::black(), // Background color - 5, // aliasing limit - 3, // reflection recursion limit - 0.0, // diffraction index - ); - } -} From 82dee9fde542283d5c6ef245a9f10c2b0871d8c1 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 7 Apr 2020 23:21:31 +0200 Subject: [PATCH 55/67] library: render: progress: fix refresh for pathtracing --- pathtracer/src/render/pathtrace/pathtracer.rs | 2 +- pathtracer/src/render/progress.rs | 26 +++++++++++++++---- pathtracer/src/render/raytrace/raytracer.rs | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index dec5700..e69184f 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -38,7 +38,7 @@ impl Pathtracer { ); let total = width * height; - let p = super::super::progress::get_progressbar(self.scene.shot_rays as u64); + let p = super::super::progress::get_passes_progressbar(self.scene.shot_rays); // Ensure at least one round of shots let img_buf = (0..self.scene.shot_rays.max(1)) diff --git a/pathtracer/src/render/progress.rs b/pathtracer/src/render/progress.rs index 4d2c398..83927fd 100644 --- a/pathtracer/src/render/progress.rs +++ b/pathtracer/src/render/progress.rs @@ -1,10 +1,26 @@ -use indicatif::ProgressBar; +use indicatif::{ProgressBar, ProgressStyle}; -pub fn get_progressbar(total: u64) -> ProgressBar { +pub fn get_progressbar(total: u64, style: &str) -> ProgressBar { let pb = ProgressBar::new(total); pb.set_draw_delta((total / 10000).max(1)); - pb.set_style(indicatif::ProgressStyle::default_bar().template( - "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", - )); + pb.set_style(ProgressStyle::default_bar().template(style)); + pb +} + +pub fn get_pixels_progressbar(total: u64) -> ProgressBar { + get_progressbar( + total, + "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} pixels (ETA: {eta})", + ) +} + +pub fn get_passes_progressbar(total: u32) -> ProgressBar { + let pb = get_progressbar( + total as u64, + "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {percent:>3}%: {pos}/{len} passes (ETA: {eta})", + ); + + pb.enable_steady_tick(1000); + pb } diff --git a/pathtracer/src/render/raytrace/raytracer.rs b/pathtracer/src/render/raytrace/raytracer.rs index 8aceb29..aa9ab6f 100644 --- a/pathtracer/src/render/raytrace/raytracer.rs +++ b/pathtracer/src/render/raytrace/raytracer.rs @@ -40,7 +40,7 @@ impl Raytracer { ); let total = (image.width() * image.height()) as u64; - let pb = super::super::progress::get_progressbar(total); + let pb = super::super::progress::get_pixels_progressbar(total); let pixel_func = if self.scene.shot_rays > 0 { Self::anti_alias_pixel From 9596bb3d00ef122e785a717732b4b03723a3b9d1 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 7 Apr 2020 23:32:34 +0200 Subject: [PATCH 56/67] library: render: pathtracer: simple progress --- pathtracer/Cargo.toml | 5 ++++- pathtracer/src/render/pathtrace/pathtracer.rs | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pathtracer/Cargo.toml b/pathtracer/Cargo.toml index f80ec3b..eaaf50b 100644 --- a/pathtracer/Cargo.toml +++ b/pathtracer/Cargo.toml @@ -23,13 +23,16 @@ beevee = { path = "../beevee" } derive_more = "0.99.3" enum_dispatch = "0.2.1" image = "0.23.0" -indicatif = "0.14.0" rand = "0.7" rayon = "1.3.0" serde_yaml = "0.8" structopt = "0.3" tobj = "1.0" +[dependencies.indicatif] +version = "0.14" +features = ["with_rayon"] + [dependencies.nalgebra] version = "0.20.0" features = ["serde-serialize"] diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index e69184f..a5612ed 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -1,3 +1,4 @@ +use indicatif::ParallelProgressIterator; use rayon::prelude::*; use super::super::utils::{buffer_to_image, sample_hemisphere}; @@ -43,6 +44,7 @@ impl Pathtracer { // Ensure at least one round of shots let img_buf = (0..self.scene.shot_rays.max(1)) .into_par_iter() + .progress_with(p) .map(|_| { let mut buffer: Vec = Vec::new(); buffer.resize_with(total as usize, LinearColor::black); @@ -53,7 +55,6 @@ impl Pathtracer { buffer[i as usize] += self.pixel_ray(x as f32, y as f32); } } - p.inc(1); // Increment the number of passes buffer }) @@ -72,8 +73,6 @@ impl Pathtracer { }, ); - p.finish(); - buffer_to_image(img_buf, self.scene.shot_rays, width, height) } From 2fe65e9bc64ac19e94e7f97995288c69d271d9a9 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Wed, 8 Apr 2020 01:15:33 +0200 Subject: [PATCH 57/67] examples: cornell-box: change FOV and positions To make sure we see the entirety of the box, with a point-of-view which is similar to the pictures that can be found online, the box has to be farther away. I also re-centered the point-light to have it beneath the lamp in the middle of the ceiling. --- pathtracer/examples/cornell-box.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pathtracer/examples/cornell-box.yaml b/pathtracer/examples/cornell-box.yaml index e6b0f46..ccbf03e 100644 --- a/pathtracer/examples/cornell-box.yaml +++ b/pathtracer/examples/cornell-box.yaml @@ -5,7 +5,7 @@ camera: origin: [0.0, 1.0, 0.0] forward: [ 0.0, 0.0, 1.0] up: [0.0, 1.0, 0.0] - fov: 90.0 + fov: 60.0 distance_to_image: 1.0 x: 2160 y: 2160 @@ -14,12 +14,12 @@ lights: ambients: - color: {r: 0.1, g: 0.1, b: 0.1} points: - - position: [-0.5, 1.0, 1.8] + - position: [0.0, 1.95, 3.2] color: {r: 1.0, g: 1.0, b: 1.0} meshes: # FIXME: make the path relative to the YAML in some way? # Easiest solution would be to chdir to the YAML's directory - obj_file: "pathtracer/examples/objs/cornell-box.obj" - translation: [0.0, 0.0, 1.0] + translation: [0.0, 0.0, 2.8] rotation: [0, 180, 0] From be1f400b342365d583e508ed99c8c4ff3f3e662c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 8 Apr 2020 17:54:45 +0200 Subject: [PATCH 58/67] library: render: pathtracer: sequential passes --- pathtracer/src/render/pathtrace/pathtracer.rs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index a5612ed..193024b 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -1,4 +1,4 @@ -use indicatif::ParallelProgressIterator; +use indicatif::ProgressIterator; use rayon::prelude::*; use super::super::utils::{buffer_to_image, sample_hemisphere}; @@ -43,23 +43,33 @@ impl Pathtracer { // Ensure at least one round of shots let img_buf = (0..self.scene.shot_rays.max(1)) - .into_par_iter() .progress_with(p) .map(|_| { let mut buffer: Vec = Vec::new(); buffer.resize_with(total as usize, LinearColor::black); - for y in 0..self.scene.camera.film().height() { - for x in 0..self.scene.camera.film().width() { - let i = x + y * self.scene.camera.film().width(); - buffer[i as usize] += self.pixel_ray(x as f32, y as f32); - } - } + (0..height) + .into_par_iter() + .map(|y| { + let mut row: Vec = Vec::new(); + row.resize_with(width as usize, LinearColor::black); - buffer + for x in 0..width { + row[x as usize] += self.pixel_ray(x as f32, y as f32); + } + + row + }) + .reduce( + || Vec::new(), + |mut buf, row| { + buf.extend(row); + buf + }, + ) }) - .reduce( - || { + .fold( + { let mut vec = Vec::new(); vec.resize_with(total as usize, LinearColor::black); vec From 00dae425d9adfd56c97b7ff5fb2b2c4aa141187d Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 8 Apr 2020 18:05:11 +0200 Subject: [PATCH 59/67] library: render: write passes in steps --- pathtracer/src/render/pathtrace/pathtracer.rs | 19 ++++++++++++++----- pathtracer/src/render/utils.rs | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index 193024b..b052642 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -33,6 +33,7 @@ impl Pathtracer { /// /// [`Scene`]: ../scene/scene/struct.Scene.html pub fn render(&self) -> RgbImage { + let steps = vec![1, 5, 50]; let (width, height) = ( self.scene.camera.film().width(), self.scene.camera.film().height(), @@ -42,7 +43,7 @@ impl Pathtracer { let p = super::super::progress::get_passes_progressbar(self.scene.shot_rays); // Ensure at least one round of shots - let img_buf = (0..self.scene.shot_rays.max(1)) + let (img_buf, _) = (0..self.scene.shot_rays.max(1)) .progress_with(p) .map(|_| { let mut buffer: Vec = Vec::new(); @@ -72,18 +73,26 @@ impl Pathtracer { { let mut vec = Vec::new(); vec.resize_with(total as usize, LinearColor::black); - vec + let count = 0usize; + (vec, count) }, - |mut acc, buf| { + |(mut acc, count), buf| { + if steps.contains(&count) { + let image = buffer_to_image(&acc, count as u32, width, height); + image + .save(format!("{}_passes.png", count)) + .expect("writing image failed!"); + } + for (i, pixel) in buf.into_iter().enumerate() { acc[i] += pixel; } - acc + (acc, count + 1) }, ); - buffer_to_image(img_buf, self.scene.shot_rays, width, height) + buffer_to_image(&img_buf, self.scene.shot_rays, width, height) } fn pixel_ray(&self, x: f32, y: f32) -> LinearColor { diff --git a/pathtracer/src/render/utils.rs b/pathtracer/src/render/utils.rs index 086a31e..18c5755 100644 --- a/pathtracer/src/render/utils.rs +++ b/pathtracer/src/render/utils.rs @@ -104,7 +104,7 @@ pub fn sample_hemisphere(normal: Unit) -> (Unit, f32) { (scattered, 1. / scattered.dot(&normal)) } -pub fn buffer_to_image(buffer: Vec, passes: u32, width: u32, height: u32) -> RgbImage { +pub fn buffer_to_image(buffer: &[LinearColor], passes: u32, width: u32, height: u32) -> RgbImage { let mut image = RgbImage::new(width, height); for (x, y, pixel) in image.enumerate_pixels_mut() { From faa1ef1fb892077e5dba79e355745fc147cd3810 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 8 Apr 2020 19:31:46 +0200 Subject: [PATCH 60/67] library: render: pathtracer: avoid row allocation --- pathtracer/src/render/pathtrace/pathtracer.rs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index b052642..2699086 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -49,25 +49,16 @@ impl Pathtracer { let mut buffer: Vec = Vec::new(); buffer.resize_with(total as usize, LinearColor::black); - (0..height) - .into_par_iter() - .map(|y| { - let mut row: Vec = Vec::new(); - row.resize_with(width as usize, LinearColor::black); - + buffer + .par_chunks_mut(width as usize) + .enumerate() + .for_each(|(y, row)| { for x in 0..width { row[x as usize] += self.pixel_ray(x as f32, y as f32); } + }); - row - }) - .reduce( - || Vec::new(), - |mut buf, row| { - buf.extend(row); - buf - }, - ) + buffer }) .fold( { From 1e7d0a28072623d27460c0b384d7faefdcd361d8 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Wed, 8 Apr 2020 20:24:15 +0200 Subject: [PATCH 61/67] library: render: pathtracer: fix off-by-one exports --- pathtracer/src/render/pathtrace/pathtracer.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index 2699086..088fe2f 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -68,6 +68,11 @@ impl Pathtracer { (vec, count) }, |(mut acc, count), buf| { + for (i, pixel) in buf.into_iter().enumerate() { + acc[i] += pixel; + } + + let count = count + 1; // Because count is 0-indexed if steps.contains(&count) { let image = buffer_to_image(&acc, count as u32, width, height); image @@ -75,11 +80,7 @@ impl Pathtracer { .expect("writing image failed!"); } - for (i, pixel) in buf.into_iter().enumerate() { - acc[i] += pixel; - } - - (acc, count + 1) + (acc, count) // Count has been updated previously }, ); From 28df5d18a40a4db18b93cb5da26cbc9544d033fe Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 8 Apr 2020 21:58:39 +0200 Subject: [PATCH 62/67] cargo: performance flags in release --- .cargo/config | 2 ++ Cargo.toml | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 .cargo/config diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..ddff440 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[build] +rustflags = ["-C", "target-cpu=native"] diff --git a/Cargo.toml b/Cargo.toml index 9bde527..a82bbca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,7 @@ members = [ "beevee", "pathtracer", ] + +[profile.release] +lto = true +codegen-units = 1 From bcaaca2c9aad1aa23f2ac186919a6f8a45403f03 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 28 Apr 2020 11:55:26 +0200 Subject: [PATCH 63/67] examples: reduce cornell-box resolution to 1080 --- pathtracer/examples/cornell-box.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pathtracer/examples/cornell-box.yaml b/pathtracer/examples/cornell-box.yaml index ccbf03e..209a344 100644 --- a/pathtracer/examples/cornell-box.yaml +++ b/pathtracer/examples/cornell-box.yaml @@ -7,8 +7,8 @@ camera: up: [0.0, 1.0, 0.0] fov: 60.0 distance_to_image: 1.0 - x: 2160 - y: 2160 + x: 1080 + y: 1080 lights: ambients: From e650655d73865c76cb53677470a052405a596a37 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 28 Apr 2020 11:55:49 +0200 Subject: [PATCH 64/67] rustfmt: remove requirement on version --- .rustfmt.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index a9721da..19ad68e 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -52,7 +52,6 @@ use_field_init_shorthand = false force_explicit_abi = true condense_wildcard_suffixes = false color = "Auto" -required_version = "1.4.13" unstable_features = false disable_all_formatting = false skip_children = false From dad5113724cf7273c68bdae66b5fd625bba7007c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Tue, 28 Apr 2020 12:19:28 +0200 Subject: [PATCH 65/67] scene: store number of intermediate steps --- pathtracer/examples/cornell-box.yaml | 4 ++++ pathtracer/src/render/pathtrace/pathtracer.rs | 3 +-- pathtracer/src/scene/mod.rs | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pathtracer/examples/cornell-box.yaml b/pathtracer/examples/cornell-box.yaml index 209a344..bbd44a4 100644 --- a/pathtracer/examples/cornell-box.yaml +++ b/pathtracer/examples/cornell-box.yaml @@ -23,3 +23,7 @@ meshes: - obj_file: "pathtracer/examples/objs/cornell-box.obj" translation: [0.0, 0.0, 2.8] rotation: [0, 180, 0] + +steps: + - 10 + - 25 diff --git a/pathtracer/src/render/pathtrace/pathtracer.rs b/pathtracer/src/render/pathtrace/pathtracer.rs index 088fe2f..538d0fc 100644 --- a/pathtracer/src/render/pathtrace/pathtracer.rs +++ b/pathtracer/src/render/pathtrace/pathtracer.rs @@ -33,7 +33,6 @@ impl Pathtracer { /// /// [`Scene`]: ../scene/scene/struct.Scene.html pub fn render(&self) -> RgbImage { - let steps = vec![1, 5, 50]; let (width, height) = ( self.scene.camera.film().width(), self.scene.camera.film().height(), @@ -73,7 +72,7 @@ impl Pathtracer { } let count = count + 1; // Because count is 0-indexed - if steps.contains(&count) { + if self.scene.steps.contains(&count) { let image = buffer_to_image(&acc, count as u32, width, height); image .save(format!("{}_passes.png", count)) diff --git a/pathtracer/src/scene/mod.rs b/pathtracer/src/scene/mod.rs index 7d4e0af..c8a3b01 100644 --- a/pathtracer/src/scene/mod.rs +++ b/pathtracer/src/scene/mod.rs @@ -26,9 +26,11 @@ pub struct Scene { pub(crate) shot_rays: u32, pub(crate) reflection_limit: u32, pub(crate) diffraction_index: f32, + pub(crate) steps: Vec, } impl Scene { + #[allow(clippy::too_many_arguments)] /// Creates a new `Scene`. /// /// # Examples @@ -62,6 +64,7 @@ impl Scene { /// 5, // amount of rays shot per pixel /// 3, // reflection recursion limit /// 0.0, // diffraction index + /// Vec::new(), // steps /// ); /// ``` pub fn new( @@ -72,6 +75,7 @@ impl Scene { shot_rays: u32, reflection_limit: u32, diffraction_index: f32, + steps: Vec, ) -> Self { let bvh = BVH::build(&mut objects); Scene { @@ -83,6 +87,7 @@ impl Scene { shot_rays, reflection_limit, diffraction_index, + steps, } } } @@ -105,6 +110,8 @@ struct SerializedScene { reflection_limit: u32, #[serde(default = "crate::serialize::default_identity")] starting_diffraction: f32, + #[serde(default)] + steps: Vec, } impl From for Scene { @@ -125,6 +132,7 @@ impl From for Scene { scene.shot_rays, scene.reflection_limit, scene.starting_diffraction, + scene.steps, ) } } @@ -153,6 +161,7 @@ mod test { 5, // aliasing limit 3, // reflection recursion limit 0.0, // diffraction index + Vec::new(), // steps ); } } From ae3931e79613449e47349730b8359f1ae8e4594d Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sat, 9 May 2020 02:05:12 +0200 Subject: [PATCH 66/67] library: render: don't return infinite weight hemisphere sampling previously could return infinite, producing NaNs when later multiplying that weight by 0. --- pathtracer/src/render/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pathtracer/src/render/utils.rs b/pathtracer/src/render/utils.rs index 18c5755..1fdbaf6 100644 --- a/pathtracer/src/render/utils.rs +++ b/pathtracer/src/render/utils.rs @@ -101,7 +101,7 @@ pub fn sample_hemisphere(normal: Unit) -> (Unit, f32) { // The probability to have picked the ray is inversely proportional to cosine of the angle with // the normal - (scattered, 1. / scattered.dot(&normal)) + (scattered, (1. / scattered.dot(&normal)).min(f32::MAX)) } pub fn buffer_to_image(buffer: &[LinearColor], passes: u32, width: u32, height: u32) -> RgbImage { From 7526e41938b6b8e0c3744a27be9ea1b05f82aea8 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sat, 9 May 2020 13:08:37 +0200 Subject: [PATCH 67/67] project: justify every dependency --- beevee/Cargo.toml | 3 +++ pathtracer/Cargo.toml | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/beevee/Cargo.toml b/beevee/Cargo.toml index c3492e6..e7f6c72 100644 --- a/beevee/Cargo.toml +++ b/beevee/Cargo.toml @@ -7,5 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +# Linear algebra basic operations and types nalgebra = "0.20" + +# High performance quicksort/quickselect pdqselect = "0.1.0" diff --git a/pathtracer/Cargo.toml b/pathtracer/Cargo.toml index eaaf50b..6bd1df6 100644 --- a/pathtracer/Cargo.toml +++ b/pathtracer/Cargo.toml @@ -19,24 +19,44 @@ name = "pathtracer" path = "src/main.rs" [dependencies] +# Our own BVH implementation beevee = { path = "../beevee" } + +# Macro to implement arithmetic operators automagically derive_more = "0.99.3" + +# Transform interfaces into enums for better performance than dynamic dispatch enum_dispatch = "0.2.1" + +# Save an image to PNG image = "0.23.0" + +# Random implementation, not part of the standard library in Rust rand = "0.7" + +# Parallelism utility functions rayon = "1.3.0" + +# YAML deserialization serde_yaml = "0.8" + +# Command-line argument parsing utilities structopt = "0.3" + +# OBJ format parser tobj = "1.0" +# Fancy terminal progress bar [dependencies.indicatif] version = "0.14" features = ["with_rayon"] +# Linear algebra basic operations and types [dependencies.nalgebra] version = "0.20.0" features = ["serde-serialize"] +# YAML deserialization [dependencies.serde] version = "1.0" features = ["derive"]