From b64c7e972c52b7d015d661866f0cf902370343e5 Mon Sep 17 00:00:00 2001 From: Julian T Date: Sat, 6 Feb 2021 23:43:06 +0100 Subject: Implement pathtracing --- src/camera/film.rs | 5 +++-- src/core/ray.rs | 8 ++++++++ src/core/spectrum.rs | 26 +++++++++++++++++++++++--- src/core/vector3.rs | 13 +++++++++++++ src/main.rs | 6 +++--- src/render.rs | 10 ++++------ src/sample/mod.rs | 26 ++++++++++++++++++++++++-- src/sample/uniform.rs | 5 ----- src/trace/mod.rs | 26 +++++++++++++++++++------- src/trace/pathtrace.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 src/trace/pathtrace.rs diff --git a/src/camera/film.rs b/src/camera/film.rs index 7193e1d..2ff7239 100644 --- a/src/camera/film.rs +++ b/src/camera/film.rs @@ -40,12 +40,13 @@ impl Pixel { } fn add(&mut self, c: &Spectrum, weight: Float) { - self.rgb += &(c * weight); + self.rgb += &(*c * weight); self.samples += 1; } fn finalize_rgb(&self) -> [u8; 3] { - let (r, g, b) = (&self.rgb / (self.samples as Float)).to_rgb(255.0); + let spc = (self.rgb / (self.samples as Float)).gamma_correct(); + let (r, g, b) = spc.to_rgb(255.0); [ r as u8, g as u8, diff --git a/src/core/ray.rs b/src/core/ray.rs index f5517ce..c944184 100644 --- a/src/core/ray.rs +++ b/src/core/ray.rs @@ -8,6 +8,14 @@ pub struct Ray { } impl Ray { + pub fn new_to(origin: Vector3f, target: Vector3f) -> Ray { + let dir = (target - origin).norm(); + Ray { + origin, + direction: dir + } + } + pub fn at(&self, t: Float) -> Vector3f { self.origin + self.direction * t } diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs index 41b3342..727a1b6 100644 --- a/src/core/spectrum.rs +++ b/src/core/spectrum.rs @@ -6,7 +6,7 @@ use std::ops; // TODO implement SampledSpectrum instead for nicer images -#[derive(Clone, Default)] +#[derive(Clone, Copy, Default)] pub struct Spectrum { c: [Float; 3], } @@ -19,9 +19,17 @@ impl Spectrum { pub fn to_rgb(&self, scale: Float) -> (Float, Float, Float) { (self.c[0] * scale, self.c[1] * scale, self.c[2] * scale) } + + pub fn gamma_correct(&self) -> Self { + Self::new_rgb( + self.c[0].sqrt(), + self.c[1].sqrt(), + self.c[2].sqrt(), + ) + } } -impl std::ops::Mul for &Spectrum { +impl std::ops::Mul for Spectrum { type Output = Spectrum; fn mul(self, op: Float) -> Self::Output { @@ -33,7 +41,7 @@ impl std::ops::Mul for &Spectrum { } } -impl std::ops::Div for &Spectrum { +impl std::ops::Div for Spectrum { type Output = Spectrum; fn div(self, op: Float) -> Self::Output { @@ -45,6 +53,18 @@ impl std::ops::Div for &Spectrum { } } +impl std::ops::Add for Spectrum { + type Output = Spectrum; + + fn add(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::AddAssign<&Self> for Spectrum { fn add_assign(&mut self, op: &Self) { self.c[0] += op.c[0]; diff --git a/src/core/vector3.rs b/src/core/vector3.rs index 10de647..ef067aa 100644 --- a/src/core/vector3.rs +++ b/src/core/vector3.rs @@ -50,6 +50,18 @@ impl Add for Vector3 { } } +impl Add for Vector3 { + type Output = Self; + + fn add(self, op: T) -> Self::Output { + Self::new_xyz( + self.x + op, + self.y + op, + self.z + op, + ) + } +} + impl Mul for Vector3 { type Output = Self; fn mul(self, op: T) -> Self::Output { @@ -114,6 +126,7 @@ impl Vector3f { /// assert!(v.x == 1.0); /// ``` pub fn norm_in(&mut self) { + // TODO Experiment with checking for normality with len_squared let len = self.len(); if len == 0.0 { *self = Self::new(0.0); diff --git a/src/main.rs b/src/main.rs index c3d11e8..c6bc14e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use pathtrace::camera::{Camera, Film, CameraSettings}; use pathtrace::scene::Scene; -use pathtrace::trace::Tracer; +use pathtrace::trace::DefaultTracer; use pathtrace::scene::shapes::Sphere; use pathtrace::core::{Vector2i, Vector3f}; use pathtrace::render::{RenderContext, RenderTask}; @@ -23,11 +23,11 @@ fn main() { Box::new(Sphere::new(100.0, Vector3f::new_xyz(0.0, -100.5, -1.0))), ]); - let tracer = Tracer::new(); + let tracer = DefaultTracer::new(&scn, None); let mut sampler = UniformSampler::new(); - let ctx = RenderContext { cam: &cam, scn: &scn, trc: &tracer }; + let ctx = RenderContext { cam: &cam, trc: &tracer }; let mut film = Film::new(res); let tile = film.get_tile(&film.frame); diff --git a/src/render.rs b/src/render.rs index 9c9e924..23b80ae 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,11 +3,10 @@ //! This is not a final design use crate::camera::film::FilmTile; use crate::camera::Camera; -use crate::scene::Scene; -use crate::trace::Tracer; +use crate::trace::{DefaultTracer, Tracer}; use crate::sample::Sampler; -use crate::core::{Vector2f, Spectrum}; +use crate::core::{Vector2f}; use crate::Float; pub struct RenderTask { @@ -16,9 +15,8 @@ pub struct RenderTask { } pub struct RenderContext<'a> { - pub scn: &'a Scene, pub cam: &'a Camera, - pub trc: &'a Tracer, + pub trc: &'a DefaultTracer<'a>, } impl RenderTask { @@ -35,7 +33,7 @@ impl RenderTask { // Create a ray let (r, _) = ctx.cam.generate_ray(&p); - self.tile.add_sample(&p, ctx.trc.trace(ctx.scn, &r)); + self.tile.add_sample(&p, ctx.trc.trace(sampler, &r)); } } diff --git a/src/sample/mod.rs b/src/sample/mod.rs index 6f2c3eb..84ac755 100644 --- a/src/sample/mod.rs +++ b/src/sample/mod.rs @@ -1,11 +1,33 @@ use crate::Float; -use crate::core::Vector2f; +use crate::core::{Vector3f, Vector2f}; +use std::f32::consts::PI; mod uniform; pub use uniform::UniformSampler; +fn distribute_between(x: Float, a: Float, b: Float) -> Float { + x * (b - a) + a +} + pub trait Sampler { fn get_sample(&mut self) -> Float; - fn get_sample_2d(&mut self) -> Vector2f; + + fn get_sample_2d(&mut self) -> Vector2f { + Vector2f::new_xy(self.get_sample(), self.get_sample()) + } + + fn get_unit_vector(&mut self) -> Vector3f { + let s2d = self.get_sample_2d(); + + let lambda = distribute_between(s2d.x, -PI, PI); + let costheta = 2.0 * s2d.y - 1.0; + let sintheta = costheta.acos().sin(); + + Vector3f::new_xyz( + lambda.cos() * sintheta, + lambda.sin() * sintheta, + costheta, + ) + } } diff --git a/src/sample/uniform.rs b/src/sample/uniform.rs index 221fdf8..cc6825a 100644 --- a/src/sample/uniform.rs +++ b/src/sample/uniform.rs @@ -1,4 +1,3 @@ -use crate::core::Vector2f; use crate::Float; use super::Sampler; @@ -25,8 +24,4 @@ impl Sampler for UniformSampler { fn get_sample(&mut self) -> Float { self.d.sample(&mut self.r) } - - fn get_sample_2d(&mut self) -> Vector2f { - Vector2f::new_xy(self.get_sample(), self.get_sample()) - } } diff --git a/src/trace/mod.rs b/src/trace/mod.rs index 9c8b0b8..7f02f6f 100644 --- a/src/trace/mod.rs +++ b/src/trace/mod.rs @@ -1,24 +1,36 @@ use crate::scene::Scene; use crate::core::{Spectrum, Ray, Vector3f}; +use crate::sample::Sampler; + +mod pathtrace; +pub use pathtrace::PathTracer; /// Simple surface normal tracer /// /// This ray tracer bases color values on the hit surface normals -pub struct NormTracer {} +pub struct NormTracer<'a> { + scn: &'a Scene, +} /// Alias for chosen trace implementation. /// /// This is swiched at compile time to save alot of time. -pub type Tracer = NormTracer; +pub type DefaultTracer<'a> = PathTracer<'a>; -impl NormTracer { - pub fn new() -> NormTracer { - NormTracer {} +pub trait Tracer { + fn trace(&self, sampler: &mut dyn Sampler, ray: &Ray) -> Spectrum; +} + +impl NormTracer<'_> { + pub fn new(scn: &Scene) -> NormTracer { + NormTracer {scn} } +} - pub fn trace(&self, scn: &Scene, ray: &Ray) -> Spectrum { +impl Tracer for NormTracer<'_> { + fn trace(&self, sampler: &mut dyn Sampler, ray: &Ray) -> Spectrum { // Trace ray - if let Some(i) = scn.intersect(ray) { + 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 new file mode 100644 index 0000000..32a8e15 --- /dev/null +++ b/src/trace/pathtrace.rs @@ -0,0 +1,41 @@ +use crate::scene::Scene; +use crate::core::{Ray, Spectrum}; +use crate::sample::Sampler; +use super::Tracer; + +pub struct PathTracer<'a> { + depth: i32, + scn: &'a Scene, +} + +impl PathTracer<'_> { + pub fn new(scn: &Scene, depth: Option) -> PathTracer { + let depth = depth.unwrap_or(-1); + + PathTracer { + depth, + scn, + } + } + + 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; + } + + // Simulates a sky + let t = (ray.direction.norm().y + 1.0) * 0.5; + Spectrum::new_rgb(1.0, 1.0, 1.0) * (1.0-t) + Spectrum::new_rgb(0.5, 0.7, 1.0) * t + + } +} + +impl Tracer for PathTracer<'_> { + fn trace(&self, sampler: &mut dyn Sampler, ray: &Ray) -> Spectrum { + self.trace_recur(sampler, ray) + } +} -- cgit v1.2.3