Skip to content

Commit

Permalink
feat(render): add PathTracer to implement path tracing algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
Paolo-Azzini committed Jun 25, 2022
1 parent 7e693a4 commit 993ec53
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/imagetracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl<'a, C: Copy + FireRay> ImageTracer<'a, C> {
/// For each pixel in the [`HdrImage`] object fire one [`Ray`],\
/// and pass it to a [`renderer`](../renderer),
/// which must implement a [`Solve`] trait.
pub fn fire_all_rays<R: Solve>(&mut self, renderer: R) {
pub fn fire_all_rays<R: Solve>(&mut self, mut renderer: R) {
for row in 0..self.image.shape().1 {
for col in 0..self.image.shape().0 {
let ray = self.fire_ray(col, row, 0.5, 0.5);
Expand All @@ -65,7 +65,7 @@ mod test {
struct DummyRenderer;

impl Solve for DummyRenderer {
fn solve(&self, _ray: Ray) -> Color {
fn solve(&mut self, _ray: Ray) -> Color {
Color::from((1.0, 2.0, 3.0))
}
}
Expand Down
19 changes: 16 additions & 3 deletions src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ impl Default for DiffuseBRDF<UniformPigment> {
}
}

impl<P: GetColor + Clone> GetColor for DiffuseBRDF<P> {
fn get_color(&self, uv: Vector2D) -> Color {
self.pigment.get_color(uv)
}
}

impl<P: GetColor + Clone> Eval for DiffuseBRDF<P> {
fn eval(&self, _normal: Normal, _in_dir: Vector, _out_dir: Vector, uv: Vector2D) -> Color {
self.pigment.get_color(uv) * (1.0 / PI)
Expand Down Expand Up @@ -159,7 +165,8 @@ impl<P: GetColor + Clone> ScatterRay for DiffuseBRDF<P> {
}

/// A class representing an ideal mirror BRDF.
pub struct SpecularBRDF<P: GetColor> {
#[derive(Clone)]
pub struct SpecularBRDF<P: GetColor + Clone> {
/// A generic pigment that implement [`GetColor`] trait.
pub pigment: P,
/// A threshold angle in radians.
Expand All @@ -175,7 +182,13 @@ impl Default for SpecularBRDF<UniformPigment> {
}
}

impl<P: GetColor> Eval for SpecularBRDF<P> {
impl<P: GetColor + Clone> GetColor for SpecularBRDF<P> {
fn get_color(&self, uv: Vector2D) -> Color {
self.pigment.get_color(uv)
}
}

impl<P: GetColor + Clone> Eval for SpecularBRDF<P> {
fn eval(&self, normal: Normal, in_dir: Vector, out_dir: Vector, uv: Vector2D) -> Color {
let theta_in = f32::acos(
Vector::from(normal)
Expand All @@ -198,7 +211,7 @@ impl<P: GetColor> Eval for SpecularBRDF<P> {
}
}

impl<P: GetColor> ScatterRay for SpecularBRDF<P> {
impl<P: GetColor + Clone> ScatterRay for SpecularBRDF<P> {
/// Perfect mirror behaviour.
fn scatter_ray(
&self,
Expand Down
111 changes: 106 additions & 5 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Provides different renderers that implement [`Solve`] trait.
use crate::color::Color;
use crate::material::{Eval, GetColor, ScatterRay};
use crate::random::Pcg;
use crate::ray::Ray;
use crate::world::World;

Expand All @@ -11,7 +12,7 @@ use crate::world::World;
/// Must accept a [`Ray`] as its only parameter and must return a [`Color`] instance telling the
/// color to assign to a pixel in the image.
pub trait Solve {
fn solve(&self, ray: Ray) -> Color;
fn solve(&mut self, ray: Ray) -> Color;
}

/// A on/off renderer.
Expand All @@ -20,7 +21,7 @@ pub trait Solve {
/// as it is really fast, but it produces boring images.
pub struct OnOffRenderer<'a, B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// A world instance.
Expand All @@ -33,7 +34,7 @@ where

impl<'a, B, P> OnOffRenderer<'a, B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Create a new [`OnOffRenderer`] renderer.
Expand All @@ -48,16 +49,116 @@ where

impl<'a, B, P> Solve for OnOffRenderer<'a, B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Solve rendering with on/off strategy.
///
/// If intersection happens return `fg_color` otherwise `bg_color`.
fn solve(&self, ray: Ray) -> Color {
fn solve(&mut self, ray: Ray) -> Color {
match self.world.ray_intersection(ray) {
Some(_hit) => self.fg_color,
None => self.bg_color,
}
}
}

/// A path tracing renderer,
///
/// it resolves the rendering equations by means
/// of a Monte Carlo numeric integration algorithm.
pub struct PathTracer<'a, B, P>
where
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// A world instance.
world: &'a World<B, P>,
/// Background color (usually [`BLACK`](../color/constant.BLACK.html)).
bg_color: Color,
/// Random number generator used by [`ScatterRay`](http://localhost:8080/material/trait.ScatterRay.html)
pcg: Pcg,
/// Number of scattered rays after every impact.
num_of_rays: u32,
/// Maximum depth of scattered rays,
/// this should always be infinite if not for debugging purposes.
max_depth: u32,
/// After this level of depth the russian roulette algorithm came into play
/// to eventually stop the rendering.
russian_roulette_limit: u32,
}

impl<'a, B, P> PathTracer<'a, B, P>
where
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Create a new [`PathTracer`] renderer.
pub fn new(
world: &World<B, P>,
bg_color: Color,
pcg: Pcg,
num_of_rays: u32,
max_depth: u32,
russian_roulette_limit: u32,
) -> PathTracer<B, P> {
PathTracer {
world,
bg_color,
pcg,
num_of_rays,
max_depth,
russian_roulette_limit,
}
}
}

impl<'a, B, P> Solve for PathTracer<'a, B, P>
where
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Solve the rendering equation using a path tracing algorithm.
///
/// The algorithm implemented here allows the caller to tune number of
/// rays thrown at each iteration, as well as the maximum depth.
/// It implements Russian roulette, so in principle it will take a finite
/// time to complete the calculation even if you set `max_depth` to infinity.
fn solve(&mut self, ray: Ray) -> Color {
if ray.depth > self.max_depth {
return Color::default();
}
let hit_record = self.world.ray_intersection(ray);
if hit_record.is_none() {
return self.bg_color;
}
let hit = hit_record.unwrap();
let hit_material = hit.material;
let mut hit_color = hit_material.brdf.get_color(hit.surface_point);
let emitted_radiance = hit_material.emitted_radiance.get_color(hit.surface_point);
let hit_color_lum = hit_color.r.max(hit_color.g.max(hit_color.b));
if ray.depth >= self.russian_roulette_limit {
let q = (1. - hit_color_lum).max(0.05);
if self.pcg.random_float() > q {
hit_color = hit_color * (1.0 / (1. - q));
} else {
return emitted_radiance;
}
}
let mut cum_radiance = Color::default();
if hit_color_lum > 0. {
for _ in 0..self.num_of_rays {
let new_ray = hit_material.brdf.scatter_ray(
&mut self.pcg,
hit.ray.dir,
hit.world_point,
hit.normal,
ray.depth + 1,
);
let new_radiance = Self::solve(self, new_ray);
cum_radiance = cum_radiance + (hit_color * new_radiance);
}
}
emitted_radiance + cum_radiance * (1. / (self.num_of_rays as f32))
}
}
18 changes: 9 additions & 9 deletions src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::f32::consts::PI;
/// calculate how [light rays](struct@Ray) hit them.
pub trait RayIntersection<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
fn ray_intersection(&self, ray: Ray) -> Option<HitRecord<B, P>>;
Expand All @@ -28,7 +28,7 @@ where
#[derive(Clone)]
pub struct HitRecord<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Coordinates of the point of impact.
Expand All @@ -47,7 +47,7 @@ where

impl<B, P> IsClose for HitRecord<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Return `true` if all the members of two [`HitRecord`](struct@HitRecord) are [close](trait@IsClose).
Expand All @@ -63,7 +63,7 @@ where
/// Geometrical shape corresponding to a sphere.
pub struct Sphere<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// A generic sphere is defined by means of a
Expand All @@ -87,7 +87,7 @@ impl Default for Sphere<DiffuseBRDF<UniformPigment>, UniformPigment> {

impl<B, P> Sphere<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Provides a constructor for [`Sphere`](struct@Sphere).
Expand Down Expand Up @@ -129,7 +129,7 @@ fn sphere_point_to_uv(point: Point) -> Vector2D {

impl<B, P> RayIntersection<B, P> for Sphere<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Finds intersections between a [`Ray`](struct@Ray) and a [`Sphere`](struct@Sphere).
Expand Down Expand Up @@ -170,7 +170,7 @@ where
/// Geometrical shape corresponding to a plane.
pub struct Plane<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// A generic plane is defined by means of a [`Transformation`](struct@Transformation)
Expand All @@ -194,7 +194,7 @@ impl Default for Plane<DiffuseBRDF<UniformPigment>, UniformPigment> {

impl<B, P> Plane<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Provides a constructor for [`Plane`](struct@Plane).
Expand Down Expand Up @@ -233,7 +233,7 @@ fn plane_point_to_uv(point: Point) -> Vector2D {

impl<B, P> RayIntersection<B, P> for Plane<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Finds intersections between a [`Ray`](struct@Ray) and a [`Plane`](struct@Plane).
Expand Down
4 changes: 2 additions & 2 deletions src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::shape::{HitRecord, RayIntersection};
#[derive(Default)]
pub struct World<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// A [`std::vec::Vec`] of [`std::boxed::Box`]-ed [`shapes`](../shape)
Expand All @@ -25,7 +25,7 @@ where

impl<B, P> World<B, P>
where
B: ScatterRay + Eval + Clone,
B: ScatterRay + Eval + GetColor + Clone,
P: GetColor + Clone,
{
/// Append a new boxed shape to this [`World`].
Expand Down

0 comments on commit 993ec53

Please sign in to comment.