🌀 Render adventures 1: first steps

My first steps writing an offline renderer in Rust.

5 minute read

Some renders made with the path tracer.
Some renders made with my path tracer.

Jump to heading First Steps

As so many people out there that want to get into writing a raytracer, I started with raytracing in one weekend. I can 100% recommend this as a starting point, even if you are not familiar with ray tracing at all.

While there exists a second and a third book, I started diverging after the first book and ended up using the later books only as a reference, instead of following them step-by-step.

Jump to heading Naive Path Tracing

After roughly following the raytracing in one weekend book, I ended up with a naive path tracer that just randomly sends out rays and results in a radiance contribution if the ray ends up hitting a light source.

Naive path tracing.
Naive path tracing.

As you can see in the image above, the result is noisy. When the ray bounces throughout the scene, it will only result in a non-zero contribution only if it happens to hit the light source. A lot of rays will simply miss the light source and not produce a contribution, which causes longer convergence times. This is illustrated in the images below where a scene is shown with a light source that varies in size (power is kept constant).

Blurred buffer outline.
(a) Indirect illumination small size light source (100spp).
Blurred buffer outline.
(b) Indirect illumination medium size light source (100spp).
Blurred buffer outline.
(c) Indirect illumination large size light source (100spp).

Because the lighting contribution depends on the ray randomly hitting the light, a small light source will rarely be hit and result in a darker scene. A large light source will be hit by most rays and the scene will be bright.

Jump to heading Explicit Light Sampling

To avoid the noisy result we got from naive path tracing, lights may be explicitly sampled. For each surface interaction, the direct lighting is explicitly evaluated by sending a shadow ray towards the light source.

Blurred buffer outline.
(a) Direct illumination.
Blurred buffer outline.
(b) Indirect illumination with explicit light sampling.

By explicitly sampling the lights, there no longer is a dependency on the probability of the light path randomly hitting a light source to get a non-zero contribution. Instead, each bounce gets a non-zero contribution because the light sources are explicitly sampled.

Blurred buffer outline.
(a) Indirect illumination without explicit light sampling (1spp).
Blurred buffer outline.
(b) Indirect illumination without explicit light sampling (49spp).
Blurred buffer outline.
(c) Indirect illumination without explicit light sampling (100spp).
Blurred buffer outline.
(a) Indirect illumination with explicit light sampling (1spp).
Blurred buffer outline.
(b) Indirect illumination with explicit light sampling (49spp).
Blurred buffer outline.
(c) Indirect illumination with explicit light sampling (100spp).

When looking at the convergence between implicit light sampling (naive path tracing) and explicit light sampling, the render converges much quicker when explicitly sampling the lights. Below, a graph is shown that quantifies the convergence by measuring the MSE (mean-squared-error).

BRDF plot for ss440 material by Ngan et al.
Convergence of direct illumination and brute-force/hybrid methods.

The term Brute-force is used for naive path tracing and the term Hybrid is used to denote the method where direct illumination is evaluated by explicitly sampling the light sources.

Jump to heading BRDF Importance Sampling

Instead of randomly generating a ray direction within the hemisphere whenever a ray bounces on a surface, an importance-sampling scheme could be used. Using a cosine-weighted sampling scheme, rays do not get randomly generated but are instead generated according to a probability proportional to a cosine-lobe (for diffuse materials). This is in an attempt to generate fewer rays that would result in a low contribution.

Blurred buffer outline.
(a) Uniform sampling (16spp).
Blurred buffer outline.
(b) Cosine-weighted sampling (16spp).

In the images above, a scene with a diffuse sphere and a white environment light are shown. By sampling proportional to the BRDF (diffuse so cosine-weighted in this case), the noise is reduced. Different sampling schemes may be used for different BRDFs. This is discussed further in my note about sampling the hemisphere. An example is shown below where a Cook-Torrance BRDF is used and both a cosine-weighted and BRDF-weighted sampling scheme are used. By properly generating samples proportionally to the Cook-Torrance BRDF, the noise is greatly reduced.

Sable.
(a) Cosine-weighted sampling.
The Last of Us.
(b) BRDF importance-sampling

Especially for sharp BRDFs, generating random samples in the hemisphere will result in a lot of samples that result in a near-zero contribution of the BRDF factor. This is illustrated in the BRDF plot below.

BRDF plot for ss440 material by Ngan et al.
BRDF plot for ss440 material by Ngan et al.

Jump to heading Materials

Support was added for several material type such as mirrors, metallic surfaces and dielectrics (glass). The combination of a glass object with an environment light results in pretty renders and caustics.

BRDF plot for ss440 material by Ngan et al.
Dielectric caustics

Jump to heading Future Work

My current toy path tracer supports much of the basics that any path tracer needs

Much work is still to be done and I have future plans for this so stay tuned!

Jump to heading Additional Resources

Jump to heading General

https://raytracing.github.io/books/RayTracingInOneWeekend.html

https://youtu.be/gfW1Fhd9u9Q

https://pbrt.org/

Jump to heading Example projects

https://aras-p.info/blog/2018/03/28/Daily-Pathtracer-Part-0-Intro/

https://www.mattkeeter.com/projects/rayray/

Published