From 33e747fa1c0957546c10e4d7b490ac7fbb0fd2d2 Mon Sep 17 00:00:00 2001 From: Julian T Date: Sun, 7 Feb 2021 14:25:06 +0100 Subject: Abstracted lambertian into material --- src/core/hittable.rs | 23 +++++++++++++++++++++++ src/core/mod.rs | 2 ++ src/core/spectrum.rs | 12 ++++++++++++ src/core/vector3.rs | 11 ++++++++++- src/lib.rs | 2 ++ src/main.rs | 18 ++++++++++++------ src/material/lambertian.rs | 37 +++++++++++++++++++++++++++++++++++++ src/material/mod.rs | 10 ++++++++++ src/scene/mod.rs | 29 +++++++++++------------------ src/scene/scene.rs | 28 ++++++++++++++-------------- src/scene/shapes/sphere.rs | 3 +-- src/trace/mod.rs | 6 +++--- src/trace/pathtrace.rs | 9 ++++----- 13 files changed, 141 insertions(+), 49 deletions(-) create mode 100644 src/core/hittable.rs create mode 100644 src/material/lambertian.rs create mode 100644 src/material/mod.rs diff --git a/src/core/hittable.rs b/src/core/hittable.rs new file mode 100644 index 0000000..20c82f7 --- /dev/null +++ b/src/core/hittable.rs @@ -0,0 +1,23 @@ +use crate::core::{Vector3f, Ray}; + +/// Returns the context of a intersection +pub struct Intersection { + /// Normal vector at intersection + pub n: Vector3f, + pub p: Vector3f, +} + +impl Intersection { + pub fn norm_against_ray(&self, r: &Ray) -> Vector3f { + if self.n.dot(&r.direction) < 0.0 { + self.n + } else { + -self.n + } + } +} + +/// Defines a common trait for objects in the scene +pub trait Hittable { + fn intersect(&self, ray: &Ray) -> Option; +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 5723b2d..c2b3771 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,6 +6,7 @@ pub mod vector2; pub mod vector3; pub mod bound; pub mod spectrum; +pub mod hittable; mod ray; pub use vector2::{Vector2i, Vector2f}; @@ -13,3 +14,4 @@ pub use vector3::Vector3f; pub use bound::{Bound2i, Bound2f}; pub use spectrum::Spectrum; pub use ray::Ray; +pub use hittable::{Hittable, Intersection}; diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs index 727a1b6..9cca7dc 100644 --- a/src/core/spectrum.rs +++ b/src/core/spectrum.rs @@ -41,6 +41,18 @@ impl std::ops::Mul for Spectrum { } } +impl std::ops::Mul for Spectrum { + type Output = Spectrum; + + fn mul(self, op: Self) -> Self::Output { + Self::Output::new_rgb( + self.c[0] * op.c[0], + self.c[1] * op.c[1], + self.c[2] * op.c[2], + ) + } +} + impl std::ops::Div for Spectrum { type Output = Spectrum; diff --git a/src/core/vector3.rs b/src/core/vector3.rs index ef067aa..ff5a678 100644 --- a/src/core/vector3.rs +++ b/src/core/vector3.rs @@ -1,7 +1,7 @@ //! Implements 3d vectors //! //! Also add more 3d math things needed for shading and 3d calculations. -use crate::{Float, Number}; +use crate::{Float, Number, NEAR_ZERO}; use std::ops::{Mul, Sub, Add, DivAssign, Neg}; use std::fmt; @@ -149,4 +149,13 @@ impl Vector3f { ) } + + /// Check if vector is close to [0, 0, 0] + /// + /// This is based on the NEAR_ZERO constant + pub fn near_zero(&self) -> bool { + (self.x.abs() < NEAR_ZERO) && + (self.y.abs() < NEAR_ZERO) && + (self.z.abs() < NEAR_ZERO) + } } diff --git a/src/lib.rs b/src/lib.rs index 7da6627..a0af705 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod render; pub mod scene; pub mod trace; pub mod sample; +pub mod material; use std::ops::{Add, Sub, Mul, DivAssign, Neg}; use std::cmp; @@ -34,3 +35,4 @@ impl Number for f64 {} pub type Float = f64; pub const M_PI: Float = PI; +pub const NEAR_ZERO: Float = 1e-8; diff --git a/src/main.rs b/src/main.rs index 1604ae3..759cbf9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,13 @@ use pathtrace::camera::{Camera, Film, CameraSettings}; -use pathtrace::scene::Scene; +use pathtrace::scene::{Scene, Object}; use pathtrace::trace::DefaultTracer; use pathtrace::scene::shapes::Sphere; -use pathtrace::core::{Vector2i, Vector3f}; +use pathtrace::core::{Vector2i, Vector3f, Spectrum}; use pathtrace::render::{RenderContext, RenderTask}; use pathtrace::sample::UniformSampler; +use pathtrace::material::Lambertian; + +use std::rc::Rc; fn main() { let res = Vector2i::new_xy(500, 500); @@ -17,10 +20,13 @@ fn main() { screensize: res, }); + let brown = Rc::new(Lambertian::new(Spectrum::new_rgb(0.5, 0.3, 0.0))); + let blue = Rc::new(Lambertian::new(Spectrum::new_rgb(0.0, 0.3, 0.7))); + let mut scn = Scene::new(); - scn.add_shapes(vec![ - Box::new(Sphere::new(0.5, Vector3f::new_xyz(0.0, 0.0, -1.0))), - Box::new(Sphere::new(100.0, Vector3f::new_xyz(0.0, -100.5, -1.0))), + scn.add_objects(vec![ + Object::new(blue.clone(), Box::new(Sphere::new(0.5, Vector3f::new_xyz(0.0, 0.0, -1.0)))), + Object::new(brown.clone(), Box::new(Sphere::new(100.0, Vector3f::new_xyz(0.0, -100.5, -1.0)))), ]); let tracer = DefaultTracer::new(&scn, None); @@ -32,7 +38,7 @@ fn main() { let mut film = Film::new(res); let tile = film.get_tile(&film.frame); - let mut task = RenderTask::new(Box::new(tile), 100); + let mut task = RenderTask::new(Box::new(tile), 10); task.render(&ctx, &mut sampler); film.commit_tile(&task.tile); diff --git a/src/material/lambertian.rs b/src/material/lambertian.rs new file mode 100644 index 0000000..28d15d3 --- /dev/null +++ b/src/material/lambertian.rs @@ -0,0 +1,37 @@ +use super::Material; +use crate::core::{Intersection, Ray, Spectrum}; +use crate::sample::Sampler; + +use std::rc::Rc; + +pub struct Lambertian { + color: Spectrum, +} + +impl Lambertian { + pub fn new(c: Spectrum) -> Lambertian { + Lambertian { + color: c, + } + } + + pub fn new_rc(c: Spectrum) -> Rc { + Rc::new(Self::new(c)) + } +} + +impl Material for Lambertian { + fn scatter(&self, ray: &Ray, i: &Intersection, sampler: &mut dyn Sampler) -> Option<(Spectrum, Ray)> { + let mut newray = Ray { + origin: i.p, + direction: i.n + sampler.get_unit_vector(), + }; + + // Make sure that the resulting direction is not (0, 0, 0) + if newray.direction.near_zero() { + newray.direction = i.n; + } + + Some((self.color.clone(), newray)) + } +} diff --git a/src/material/mod.rs b/src/material/mod.rs new file mode 100644 index 0000000..62cbf9a --- /dev/null +++ b/src/material/mod.rs @@ -0,0 +1,10 @@ +use crate::core::{Ray, Intersection, Spectrum}; +use crate::sample::Sampler; + +mod lambertian; + +pub use lambertian::Lambertian; + +pub trait Material { + fn scatter(&self, ray: &Ray, i: &Intersection, sampler: &mut dyn Sampler) -> Option<(Spectrum, Ray)>; +} diff --git a/src/scene/mod.rs b/src/scene/mod.rs index f8c1e09..cd07236 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -6,27 +6,20 @@ pub mod shapes; mod scene; pub use scene::*; -use crate::core::{Ray, Vector3f}; -use crate::Float; +use std::rc::Rc; +use crate::core::Hittable; +use crate::material::Material; -/// Returns the context of a intersection -pub struct Intersection { - /// Normal vector at intersection - pub n: Vector3f, - pub p: Vector3f, +pub struct Object { + pub shape: Box, + pub mat: Rc, } -impl Intersection { - pub fn norm_against_ray(&self, r: &Ray) -> Vector3f { - if self.n.dot(&r.direction) < 0.0 { - self.n - } else { - -self.n +impl Object { + pub fn new(mat: Rc, shape: Box) -> Self { + Object { + mat, + shape, } } } - -/// Defines a common trait for objects in the scene -pub trait Hittable { - fn intersect(&self, ray: &Ray) -> Option; -} diff --git a/src/scene/scene.rs b/src/scene/scene.rs index 367003e..0ffbe97 100644 --- a/src/scene/scene.rs +++ b/src/scene/scene.rs @@ -1,33 +1,33 @@ -use super::{Intersection, Hittable}; -use crate::core::Ray; +use crate::core::{Ray, Intersection, Hittable}; +use crate::material::Material; -type Shape = Box; +use super::Object; pub struct Scene { - shps: Vec, + objs: Vec, } impl Scene { pub fn new() -> Self { Self { - shps: Vec::new(), + objs: Vec::new(), } } - pub fn add_shape(&mut self, shp: Shape) { - self.shps.push(shp); + pub fn add_object(&mut self, obj: Object) { + self.objs.push(obj); } - pub fn add_shapes(&mut self, shps: Vec) { - for shp in shps { - self.add_shape(shp); + pub fn add_objects(&mut self, objs: Vec) { + for obj in objs { + self.add_object(obj); } } - pub fn intersect(&self, ray: &Ray) -> Option { - for shp in self.shps.iter() { - if let Some(i) = shp.intersect(&ray) { - return Some(i) + pub fn intersect(&self, ray: &Ray) -> Option<(&dyn Material, Intersection)> { + for obj in self.objs.iter() { + if let Some(i) = obj.shape.intersect(&ray) { + return Some((obj.mat.as_ref(), i)) } } diff --git a/src/scene/shapes/sphere.rs b/src/scene/shapes/sphere.rs index acca2b7..3f07b9e 100644 --- a/src/scene/shapes/sphere.rs +++ b/src/scene/shapes/sphere.rs @@ -2,8 +2,7 @@ //! //! Spheres are relatively easy to calculate intersections between use crate::Float; -use crate::core::{Ray, Vector3f}; -use crate::scene::{Hittable, Intersection}; +use crate::core::{Ray, Vector3f, Hittable, Intersection}; pub struct Sphere { radius: Float, diff --git a/src/trace/mod.rs b/src/trace/mod.rs index 7f02f6f..cf06246 100644 --- a/src/trace/mod.rs +++ b/src/trace/mod.rs @@ -28,9 +28,9 @@ impl NormTracer<'_> { } impl Tracer for NormTracer<'_> { - fn trace(&self, sampler: &mut dyn Sampler, ray: &Ray) -> Spectrum { - // Trace ray - if let Some(i) = self.scn.intersect(ray) { + fn trace(&self, _: &mut dyn Sampler, ray: &Ray) -> Spectrum { + // Trace ray, we dont care about material + if let Some((_, i)) = self.scn.intersect(ray) { let norm = i.n * 0.5 + Vector3f::new(0.5); return Spectrum::new_rgb(norm.x, norm.y, norm.z); diff --git a/src/trace/pathtrace.rs b/src/trace/pathtrace.rs index 32a8e15..a3a688a 100644 --- a/src/trace/pathtrace.rs +++ b/src/trace/pathtrace.rs @@ -20,11 +20,10 @@ impl PathTracer<'_> { pub fn trace_recur(&self, sampler: &mut dyn Sampler, ray: &Ray) -> Spectrum { - if let Some(i) = self.scn.intersect(ray) { - // Get a random direction in the hemisphere a i.p - // This is Lambertian reflection - let target = i.p + i.n + sampler.get_unit_vector(); - return self.trace_recur(sampler, &Ray::new_to(i.p, target)) * 0.5; + if let Some((mat, i)) = self.scn.intersect(ray) { + if let Some((scalar, nray)) = mat.scatter(ray, &i, sampler) { + return self.trace_recur(sampler, &nray) * scalar; + } } // Simulates a sky -- cgit v1.2.3