1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Defines the Path integrator which implements path tracing with
//! explicit light sampling
//!
//! See [Kajiya, The Rendering Equation](http://dl.acm.org/citation.cfm?id=15902)
//!
//! # Scene Usage Example
//! The pathtracer integrator needs a maximum ray depth to terminate rays at and
//! a minimum ray depth to start applying Russian Roulette to terminate rays early.
//!
//! ```json
//! "integrator": {
//!     "type": "pathtracer",
//!     "min_depth": 3,
//!     "max_depth": 8
//! }
//! ```

use std::f32;
use rand::{StdRng, Rng};
use light_arena::Allocator;

use scene::Scene;
use linalg::{self, Ray};
use geometry::{Intersection, Emitter, Instance};
use film::Colorf;
use integrator::Integrator;
use bxdf::BxDFType;
use sampler::{Sampler, Sample};

/// The path integrator implementing Path tracing with explicit light sampling
#[derive(Clone, Copy, Debug)]
pub struct Path {
    min_depth: usize,
    max_depth: usize,
}

impl Path {
    /// Create a new path integrator with the min and max length desired for paths
    pub fn new(min_depth: u32, max_depth: u32) -> Path {
        Path { min_depth: min_depth as usize, max_depth: max_depth as usize }
    }
}

impl Integrator for Path {
    fn illumination(&self, scene: &Scene, light_list: &[&Emitter], r: &Ray,
                    hit: &Intersection, sampler: &mut Sampler, rng: &mut StdRng,
                    alloc: &Allocator) -> Colorf {
        let num_samples = self.max_depth as usize + 1;
        let l_samples = alloc.alloc_slice::<(f32, f32)>(num_samples);
        let l_samples_comp = alloc.alloc_slice::<f32>(num_samples);
        let bsdf_samples = alloc.alloc_slice::<(f32, f32)>(num_samples);
        let bsdf_samples_comp = alloc.alloc_slice::<f32>(num_samples);
        let path_samples = alloc.alloc_slice::<(f32, f32)>(num_samples);
        let path_samples_comp = alloc.alloc_slice::<f32>(num_samples);
        sampler.get_samples_2d(l_samples, rng);
        sampler.get_samples_2d(bsdf_samples, rng);
        sampler.get_samples_2d(path_samples, rng);
        sampler.get_samples_1d(l_samples_comp, rng);
        sampler.get_samples_1d(bsdf_samples_comp, rng);
        sampler.get_samples_1d(path_samples_comp, rng);

        let mut illum = Colorf::black();
        let mut path_throughput = Colorf::broadcast(1.0);
        // Track if the previous bounce was a specular one
        let mut specular_bounce = false;
        let mut current_hit = *hit;
        let mut ray = *r;
        let mut bounce = 0;
        loop {
            if bounce == 0 || specular_bounce {
                if let Instance::Emitter(ref e) = *current_hit.instance {
                    let w = -ray.d;
                    illum = illum + path_throughput * e.radiance(&w, &hit.dg.p, &hit.dg.ng, ray.time);
                }
            }
            let bsdf = current_hit.material.bsdf(&current_hit, alloc);
            let w_o = -ray.d;
            let light_sample = Sample::new(&l_samples[bounce], l_samples_comp[bounce]);
            let bsdf_sample = Sample::new(&bsdf_samples[bounce], bsdf_samples_comp[bounce]);
            let li = self.sample_one_light(scene, light_list, &w_o, &current_hit.dg.p, &bsdf,
                                           &light_sample, &bsdf_sample, ray.time);
            illum = illum + path_throughput * li;

            // Determine the next direction to take the path by sampling the BSDF
            let path_sample = Sample::new(&path_samples[bounce], path_samples_comp[bounce]);
            let (f, w_i, pdf, sampled_type) = bsdf.sample(&w_o, BxDFType::all(), &path_sample);
            if f.is_black() || pdf == 0.0 {
                break;
            }
            specular_bounce = sampled_type.contains(&BxDFType::Specular);
            path_throughput = path_throughput * f * f32::abs(linalg::dot(&w_i, &bsdf.n)) / pdf;

            // Check if we're beyond the min depth at which point we start trying to
            // terminate rays using Russian Roulette
            // TODO: Am I re-weighting properly? The Russian roulette results don't look quite as
            // nice, eg. damping light in transparent objects and such.
            if bounce > self.min_depth {
                let cont_prob = f32::max(0.5, path_throughput.luminance());
                if rng.next_f32() > cont_prob {
                    break;
                }
                // Re-weight the sum terms accordingly with the Russian roulette weight
                path_throughput = path_throughput / cont_prob;
            }
            if bounce == self.max_depth {
                break;
            }

            ray = ray.child(&bsdf.p, &w_i.normalized());
            ray.min_t = 0.001;
            // Find the next vertex on the path
            match scene.intersect(&mut ray) {
                Some(h) => current_hit = h,
                None => break,
            }
            bounce += 1;
        }
        illum
    }
}