Albedo to EARS, Pt. 2 - Same Thing, But Worse
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:
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:
-
Philip Dutré’s Global Illumination Compendium
-
PBRT’s chapter on Monte Carlo Integration
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:
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 inpathtracer
.
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.