aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian T <julian@jtle.dk>2021-02-06 23:43:06 +0100
committerJulian T <julian@jtle.dk>2021-02-06 23:43:06 +0100
commitb64c7e972c52b7d015d661866f0cf902370343e5 (patch)
tree8d3dc9a8ae6b491b9f8f639f2d0bad6387d59069
parent0d5e6bd9363d5ed5c4f28174819fc0f5fd9aa586 (diff)
Implement pathtracing
-rw-r--r--src/camera/film.rs5
-rw-r--r--src/core/ray.rs8
-rw-r--r--src/core/spectrum.rs26
-rw-r--r--src/core/vector3.rs13
-rw-r--r--src/main.rs6
-rw-r--r--src/render.rs10
-rw-r--r--src/sample/mod.rs26
-rw-r--r--src/sample/uniform.rs5
-rw-r--r--src/trace/mod.rs26
-rw-r--r--src/trace/pathtrace.rs41
10 files changed, 138 insertions, 28 deletions
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<Float> for &Spectrum {
+impl std::ops::Mul<Float> for Spectrum {
type Output = Spectrum;
fn mul(self, op: Float) -> Self::Output {
@@ -33,7 +41,7 @@ impl std::ops::Mul<Float> for &Spectrum {
}
}
-impl std::ops::Div<Float> for &Spectrum {
+impl std::ops::Div<Float> for Spectrum {
type Output = Spectrum;
fn div(self, op: Float) -> Self::Output {
@@ -45,6 +53,18 @@ impl std::ops::Div<Float> 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<T: Number> Add for Vector3<T> {
}
}
+impl<T: Number> Add<T> for Vector3<T> {
+ type Output = Self;
+
+ fn add(self, op: T) -> Self::Output {
+ Self::new_xyz(
+ self.x + op,
+ self.y + op,
+ self.z + op,
+ )
+ }
+}
+
impl<T: Number> Mul<T> for Vector3<T> {
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<i32>) -> 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)
+ }
+}