cornellbox_prism_light_phot.png

Welcome to the final phase of the homeworks! Today we will dive into the world of path tracing. So far we were only performing direct lighting with our ray tracer, i.e. connecting hit points directly to the light sources. This leads to dark areas when a ray emitted from a light source cannot see particular areas. What’s more, we couldn’t simulate some particular effects such as color bleeding or caustics with direct lighting. With the introduction of path tracing, we will be able to observe these intriguing effects!

BRDFs

Before we begin, let’s formalize our notion of materials for the better. Up until now, we have been loosely using diffuse/specular/ambient reflectance coefficients to define how the surface material reflects an incoming ray. From now on, we will use Bidirectional Reflectance Distribution Functions (BRDFs) to compute our reflected rays. Remember our rendering equation (which is important, we always come back to it):

$$ L_r(x,w_o)=\int_{\omega} L_i(x,w_i)f(x,w_i,w_o)cos(\theta_i)dw_i $$

where $w_i$ is incoming ray (actually minus of incoming ray), $w_o$ is the outgoing ray, and $\theta_i$ is the angle between $w_i$ and the surface normal. BRDF is the function $f(\cdot)$ that we will define for surface reflection. Notice that there can be many definitions for BRDF function $f(\cdot)$, and this is where Rust’s trait system becomes handy.

In the following sections we will define particular BRDFs, yet they all share the same function signature. To make our code flexible, let’s define a BRDF trait that hopefully a new BRDF can make use of it in future:

pub trait BRDF {
    fn eval(
            &self,
            wi: Vector3,
            wo: Vector3,
            n: Vector3,
            material: &HeapAllocMaterial,
    ) -> Vector3;
}

where material is also a trait, with a function reflectance_data() allowing us to access diffuse and specular reflectance coefficients. It could be cleaner if reflectance coefficients were directly passed in the function signature, perhaps that is something to be considered in the upcoming refactors. For now the material is needed to access different kinds of material data, e.g. for Phong BRDF reflectance coefficients will be used but for Torrance-Sparrow refractive index will also be relevant. Yet I am also aware that it is not good use of Rust traits to access a particular data, so the future refactors should avoid such usage of traits.

Now, in order to make use of this trait, we need to declare structs that implements BRDF trait. The structs will hold their own data to be used in their particular BRDF implementation. What’s more, if they don’t need any additional data they can just be declared empty:

struct myCoolBRDF;

and later on

impl BRDF for myCoolBRDF {
	 fn eval( 
					 // some parameters
					 ) 
					 -> Vector3 {
					 
					 // some cool implementation
	 }
}

Let’s also define particular BRDFs to render some actual scenes.