Happy Lunar New Year!

As the semester begins to gain some momentum I have been focused on implementing the core path tracing algorithms that will serve as the foundation for future explorations of roulette, splitting, ADDRS and EARS. This post is focused on the front-loaded work of getting a tracing foundation stable to enable future exploration of EARS and other techniques.

One significant change from the last post is that I have discarded GLM as the mathematical core for this project in favor of Tungsten’s mathematical primitives and utilities. The great advantage of this change is that it allows me to directly graft segments of Tungsten’s path tracing algorithms without having to comb through the decimals to find subtle differences in handling coordinate systems.

From here out, I will graft algorithms and functions from Tungsten to solve problems where possible. There will be some massaging of interfaces to fit them into the existing Scene/render hierarchy. Because of its significant usage in this project I have added Tungsten’s license to this project’s license and will credit it for the algorithms used.

Project proposal: project-proposal.pdf

First post in the series: Directed Research at USC

GitHub repository: roblesch/roulette

Primitives

The goal is to render the “Cornell Box” scene provided on Benedikt’s Rendering Resources page. This scene allows us to iterate quickly by demonstrating various effects with minimal primitives. In this case, the only primitives required to intersect the scene are Rectangle and Cube.

Rectangle

A rectangle is a unit square in the XZ plane with a linear transform to the world coordinate system. Intersections are calculated by transforming an intersecting ray into the rectangle’s model space, intersecting it with the XZ plane, and checking if the intersection lies within the base and edges.

Original implementation here: tungsten/Quad.cpp

Cube

A cube is a unit cube centered on the origin with vertices at (-1,-1,-1) and (1,1,1). Intersections are calculated by transforming an itnersecting ray into the cube’s model space, checking the ray’s intersections with each of the XY, YZ, XZ planes, and seeing which is the nearest intersection that lies within the cube’s boundaries.

Original implementation here: tungsten/Cube.cpp

Normals

For now, primitives are defined as having constant normals tangent to the face across the surface. As such, no interpolation or uv mapping is required - for a rectangle, simply transform the unit positive normal to the XZ plane to world coordinates. For the cube, find which face is closest to the point of intersection and transform that face’s unit normal to the world coordinates.

Intersecting these primitives and mapping surface normals to rgb produces the following result:

normals

Note some artifacting at the edges. This is most likely a floating point precision issue, but is not critically important at the moment.

Materials

This scene requires only simple Lambertian BSDFs. References widely available, I relied on description in pbrt and Tungsten’s LambertBSDF.

Lights are represented as primitives with an Emitter, which makes available the light’s radiance.

Path Tracing

Now that the primitives are defined, Tungsten’s core path tracing loop can be grafted onto this project. I found it helpful to begin with some review of path tracing fundamentals.

Some useful materials:

Turning back the clock on Tungsten

Tungsten is a very mature project that demonstrates a variety of state-of-the-art and research algorithms. To reduce complexity, I found it helpful to trace back to a much older commit, a311923 - the earliest that I could build on my environment. I may choose to move the code forward at a later time.

Implementation

Implementation was grafted as directly as possible from tungsten/PathTracer.cpp and tungsten/TraceBase.cpp. My implementation is available here. I’d like to do some further massaging and cleanup to raise bsdf, surface and direct light sampling consistently to the Primitive interface.

With some significant headscratching before realizing I needed to tonemap my outputs, here are my current results at a stunning 1 sample per pixel:

comparison
Left, reference render in Tungsten, 1 sample per pixel. Right, render in current implementation, 1 sample per pixel.

Hmm… not quite right. Something is clearly wrong - many of the pixels show far too much variance; the light should be consistently white, and we shouldn’t have as many bright spots in dark areas.

Still, this is very close. With a bit more debugging and the addition of more samples per pixel, we should see a much cleaner result.

Next Steps

  • Debug noisy pixels
  • Swap baked-in uniform sampling for a sampler class - this will allow us to swap in more interesting samplers down the line, and seeded sampling will give us determinable results.
  • Code cleanup - raise sampling to the primitive interface and organize the methods in pathtracer.


Next time, I’ll dedicate time to a deeper discussion of ADDRS and EARS and provide some recap on the ongoing bugfixes. With much of the core development now out of the way, I can begin to transition my time more towards discussion of novel techniques.