Various projects and musings by Mason Smith
The purpose of this assigment was to create a Monte Carlo path tracer to numerically solve the rendering equation [Kajiya, 86]. Path tracing essentially by calculating the radiance along various random walks in the scene and combining the results, apropriately weighted by the probability of the walks.
The path tracer was written in Common Lisp, as an extension of t-ray, my pre-existing raytracer. In this document, I'll refer to the path tracer itself as simply "t-ray". All of the development and testing was done using SBCL 1.0.30.* on my Ubunutu machine, but as far as I am aware there is no implementation or platform-specific code.
You can download the code
here. (You can also download some example models for use with t-ray here.) The code depends on the
alexandria, iterate, metabang-bind,
cl-mesh, and pcall. cl-mesh is a library
I wrote to load .OBJ files and is included in with t-ray. pcall is
available on its
homepage. The other libraries are ASDF-installable. Readers of the code will
probably want to start at path-tracer.lisp, which contains the core
algorithm and lighting calculations.
At each bounce, I perform sampling for direct and indirect lighting. To not overestimate the direct lighting contribution, then, emitted light added to the contribution when the ray is from a camera or a specular bounce from a BRDF with a delta distribution. (I actually don't have any pure specular BRDFs implemented yet, but for future reference this will be the case.) This method of partitioning contributions is used in pbrt, from which many of my design decisions were based [Pharr 04].
Instead of storing a the location of each bounce, I just keep an accumulated total of the radiance along the path. This would have to be changed if I decided to implement certain extensions to path tracing, such as bi-directional path tracing [Lafortune, 93].
t-ray is capable of keeping accounts of direct and indirect illumination separately. This is useful for illustrative purposes, but mainly it is a necessity to perform noise filtering.
t-ray breaks down the image rendering in tasks by column, and uses a thread pool to render tasks in parallel. (Like ray tracing, path tracing is one of the easiest tasks to parallelize.) On my two-core machine, I typically get a 170%-190% CPU usage. This method could allow, at some point, for the renderer to display columns of the solution live as the rendering progressed. (One could also separate the execution into chunks of full-image size, with 1 sample per pixel each. This way, the user could see full image progressively decrease in variance.)
Unless otherwise specified, all images shown were rendered with t-ray's path tracer with no external post-processing. Russian Roulette was used to terminate paths with a 10% likelihood, starting after the third bounce. A maximum depth of 10 was also imposed. Also, only 1 sample per light is used.
t-ray has a material system which allows for linear combinations of BRDFs. Right now, only spatially-independent BRDFs are supported (which means no texture maps, etc.), but I plan to support them in the future. BRDFs can be added by implementing an evaluation function and a sampling method. For instance, Lambertian BRDFs sample from a cosine-weighted hemisphere. So far, I have implemented Lambertian, Phong, and Oren-Nayar BRDFs.
The Oren-Nayar BRDF is a generalization of the Lambertian BRDF, which treats a surface as collection of v-shaped cavities with Gaussian-distributed slopes [Oren, 94]. Each facet is assumed to be Lambertian. The resulting approximation function gives more realistic results, physically speaking, than the Lambertian model.
Every BRDF I've implemented has a tailored importance function, but I don't have
an architecure yet for importance sampling arbitrarily-specified BRDFs. All of
the materials used and their underlying BRDFs are implemented in brdf.lisp
t-ray uses lightweight BVHs, as specified in [Cline, 06], to accelerate the rendering of triangle meshes. (At some point, LBVHs will be implemented for the entire scene). LBVHs are internally implemented as heaps, so they are guaranteed to be perfectly balanced and use significantly less memory than normal BVHs.
In my implementation, a branch factor of 4 is used, and one triangle is stored per leaf node. One can store more primitives per leaf node to save memory at the cost of increased rendering time.
Most of the implementation is in shapes/lbvh.lisp and shapes/trimesh.lisp
t-ray supports point lights, area lights, and (in a somewhat ad-hoc fashion) infinite area lights. Area lights can be created from any shape that can be sampled. (This currently includes spheres, discs, and individual triangles.) Infinite area lights can support arbitrary light distributions, but they are not importance samples, so they are only really useful for constant color sources.
t-ray allows you to choose the type of sampler you want to use for sampling camera rays or (non-delta) light sources. Currently, stratified sampling is implemented in two dimensions, and Latin Hypercube sampling is implemented in one, two, and four dimensions. Latin Hypercube sampling partitions the d-dimensional unit cube into an n by n by .. by n grid, and distributes jittered samples such that each axis-aligned hyperplane has exactly one sample. In simpler terms, it places the n samples in a d-dimensional Latin Hypercube, jittering each sample within its sub-unit.
LHS is convenient since there is no restriction on (n, d). Stratified sampling, by contrast, places a restriction that n = xd, which becomes an issue for d = 4. It also avoids the worst-case issue with stratified sampling, where n*d samples are projected into a small area. On the other hand, Veach's thesis shows that stratified sampling has a better variance convergence rate in two dimensions [Veach, 97], and the effectiveness of LHS only decreases as d increases (compared to stratified).
By sampling the pixel area for individual pixels with computing camera rays, one can achieve anti-aliasing.
By sampling an area light source, we can render soft shadows in the image, rather than the hard shadows characteristic of basic Whitted ray-tracing.
By sampling the camera's aperture, t-ray can also simulate depth-of-field.
Path tracing images are subject to noise due to variance in the integration estimates. One way to reduce the variance is by simply applying a smoothing filter on the resulting image. However, this causes noticeable blurring in the image, particularly on edges. One alternative, proposed by Jensen in [Jensen, 95], is to apply a noise-reduction filter only to the contribution from at least two diffuse bounces. The paper gives empirical evidence that using a 3x3 low-pass on this portion gives an image with a lower RMS error than the original image (as compared to a reference image). t-ray implements this method for noise reduction.
Currently, t-ray is much slower than I would like it to be. Implementing LBVH for the entire scene will improve this somewhat. The running times were also from a non-release build, so adding optimization flags to the compiler might have large impact on rendering times.
I would like to add a number of BRDFS to be able to simulate a wider variety of surfaces. Perfect specular BRDFs and BTDFs would be first, followed by more realistic microfacet models and Fresnel-weighted reflections. Combined with appropriate image-based and procedural texture maps, this would allow for a more diverse set of scenes to be rendered. More fundamentally, I would like to implement photon mapping or bi-directional path tracing. Due to the current setup, photon mapping might be easier to implement initially. (I actually already implemented a kd-tree for photon storage, though that code is not included here). Irradiacne caching would also greatly improve the image results.
t-ray was extended to implement path tracing. In addition to the required parts of the assignment, I implemented multiple forms of robust integration, multiple sampler types, noise reduction filtering, and lightweight bounding volume hierarchies for accelerated triangle mesh rendering.
Models were found from Hughes Hoppe, the Caltech Multi-Res Modeling Group, the National Research Council of Canada, and the Stanford 3D Scanning Repository via the Mesh Compendium.