Release Notes for v0.5.1
2019-02-19Documentation
I provide documentation for the latest official release and intermediate (between releases) updates. The main difference is that one links to source code (for example to the SPPMPixel struct), whereas the other does not, but is updated more frequently.
Stochastic Progressive Photon Mapping (SPPM)
The biggest changes for this release is that we support Stochastic Progressive Photon Mapping (SPPM) now (see also paper). Here an example scene (or a whole repository full of them):
The details about the progress and problems I ran into are documented in the related issue #86.
Lesson(s) Learned
One interesting detail mentioned in issue #86 above is that there
was a major bottleneck slowing down the executable and after
investigating a bit using perf I figured out that the
HaltonSampler
was part of the problem. In C++ (the original code)
all instances of the class share a static vector<uint16_t>
:
class HaltonSampler : public GlobalSampler {
...
private:
// HaltonSampler Private Data
static std::vector<uint16_t> radicalInversePermutations;
...
};
In the Programming Rust book I found a solution for that problem. In chapter 17 there is a section talking about Building Regex Values Lazily, which mentions a crate called lazy_static. In chapter 19 there is a section about Global Variables, which mentions that static initializers (in Rust) can not call functions, but that the lazy_static crate can help to get around this problem. So the solution looks like this:
/// Generate random digit permutations for Halton sampler
lazy_static! {
#[derive(Debug)]
static ref RADICAL_INVERSE_PERMUTATIONS: Vec<u16> = {
let mut rng: Rng = Rng::new();
let radical_inverse_permutations: Vec<u16> = compute_radical_inverse_permutations(&mut rng);
radical_inverse_permutations
};
}
Defining a variable with the lazy_static!
macro lets you use any
expression you like to initialize it; it runs the first time the
variable is dereferenced, and the value is saved for all subsequent
uses.
The problem with SPPM was that it creates a HaltonSampler
once, but
clones it many times:
pub fn render_sppm(
scene: &Scene,
camera: &Arc<Camera + Send + Sync>,
_sampler: &mut Box<Sampler + Send + Sync>,
integrator: &mut Box<SPPMIntegrator>,
num_threads: u8,
) {
...
let mut sampler: Box<HaltonSampler> = Box::new(HaltonSampler::new(
integrator.n_iterations as i64,
pixel_bounds,
false,
));
...
for iteration in pbr::PbIter::new(0..integrator.n_iterations) {
...
let mut tile_sampler = sampler.clone();
...
}
...
}
New module, crates and structs
If you are interested in the details about new modules, structs, traits, and functions added by this release, here are some links to the documentation:
-
The module sppm contains four structs: The
SPPMIntegrator
which basically stores the parameters described here for that particular integrator.SPPMPixel
records (among other things) the weighted sum of emitted and reflected direct illumination for all camera path vertices for the pixel. It also contains information about aVisiblePoint
structure, which records a point found along a camera path at which we’ll look for nearby photons during the photon shooting pass. Finally, theSPPMPixelListNode
struct is used to create a linked list within a grid (see details here). -
Beside the
lazy_static
crate mentioned abovers-pbrt
uses now the atom crate. It provides theAtom
andAtomSetOnce
structs.
It is interesting how the grid first builds a vector of linked lists
by using Atom
for the first entry, but AtomSetOnce
for all linked
nodes:
pub struct SPPMPixelListNode<'p> {
pub pixel: &'p SPPMPixel,
pub next: AtomSetOnce<Arc<SPPMPixelListNode<'p>>>,
}
...
let mut grid: Vec<Atom<Arc<SPPMPixelListNode>>> = Vec::with_capacity(hash_size);
...
// add pixel's visible point to applicable grid cells
...
// add visible point to grid cell $(x, y, z)$
let h: usize = hash(
&Point3i { x: x, y: y, z: z },
hash_size as i32,
);
let mut node_arc =
Arc::new(SPPMPixelListNode::new(pixel));
let old_opt = grid[h].swap(node_arc.clone());
if let Some(old) = old_opt {
node_arc.next.set_if_none(old);
}
Later that grid gets replaced by a vector which replaces the first
Atom
list entry by AtomSetOnce
:
let mut grid_once: Vec<AtomSetOnce<Arc<SPPMPixelListNode>>> =
Vec::with_capacity(hash_size);
...
for h in 0..hash_size {
// take
let opt = grid[h].take();
if let Some(p) = opt {
grid_once[h].set_if_none(p);
}
}
...
if !grid_once[h].is_none() {
let mut opt = grid_once[h].get();
loop {
// deal with linked list
if let Some(node) = opt {
...
// update opt
opt = node.next.get();
...
// update opt
opt = node.next.get();
...
}
}
Minor changes
All cameras inplementing the Camera
trait have to implement two
new methods now:
pub trait Camera {
...
fn get_shutter_open(&self) -> Float;
fn get_shutter_close(&self) -> Float;
...
}
The struct Film
has two additional methods:
impl Film {
...
pub fn get_cropped_pixel_bounds(&self) -> Bounds2i {
self.cropped_pixel_bounds.clone()
}
...
pub fn set_image(&self, img: &[Spectrum]) {
...
}
I converted core::integrator::uniform_sample_one_light()
to a
generic function:
pub fn uniform_sample_one_light<S: Sampler + Send + Sync + ?Sized>(
it: &SurfaceInteraction,
scene: &Scene,
sampler: &mut Box<S>,
handle_media: bool,
light_distrib: Option<&Distribution1D>,
) -> Spectrum {
...
}
This should make it easier to call that function for implementors of
the Sampler
trait, e.g. with a pointer to a HaltonSampler
.
The trait GlobalSampler
demands a method now:
pub trait GlobalSampler: Sampler {
fn set_sample_number(&mut self, sample_num: i64) -> bool;
}
That currently affects SobolSampler
and HaltonSampler
:
$ rg -trust set_sample_number -B 1 ~/git/github/rs_pbrt
/home/jan/git/github/rs_pbrt/src/samplers/sobol.rs
211-impl GlobalSampler for SobolSampler {
212: fn set_sample_number(&mut self, sample_num: i64) -> bool {
/home/jan/git/github/rs_pbrt/src/samplers/halton.rs
300-impl GlobalSampler for HaltonSampler {
301: fn set_sample_number(&mut self, sample_num: i64) -> bool {
/home/jan/git/github/rs_pbrt/src/core/sampler.rs
38-pub trait GlobalSampler: Sampler {
39: fn set_sample_number(&mut self, sample_num: i64) -> bool;
/home/jan/git/github/rs_pbrt/src/integrators/sppm.rs
220- tile_sampler.start_pixel(&p_pixel);
221: tile_sampler.set_sample_number(iteration as i64);
The End
I hope I didn't forget anything important. Have fun and enjoy the v0.5.1 release.