Switch to PBR

 

Code so Far

Here is the code for this section

Switch to Physically Based Rendering (PBR)

The scene is setup with basic lighting, and it is running pretty fast (thousands of fps), but we aren't using the power of raytracing other than generating shadows. To really show off my ray tracer I want to support global illumination. What I wanted to focus on this week was switching over to a more realistic model of how light works. For this, I needed to implement a model that estimates the rendering equation. For reference, the rendering equation is


, and the part under the integral is the equation that needs to be estimated.

Monte Carlo 

My first attempt at estimating will be to implement a Monte Carlo estimator, Fn. What the Monte Carlo algorithm will do is for every surface position hit by a ray, a new ray will get fired in a random direction. The process of firing rays will continue recursively until a maximum number of hits is reached. I will performance this process multiple times, or n times, per pixel. I then will add all the samples and average them out. The reason this equation works is because the expected value of the estimator, E[Fn], is equal to the integral over the range sampled.

Monte Carlo Estimator



Expected Value of Monte Carlo Estimator Equals Integral Over f(x)

[a, b] is the range of random variables, Xi is a uniform random variable from the range [a, b], n is the number of samples taken, and p(x) is the probability density function (PDF).

The probability density function for Monte Carlo is a constant number because the random number is chosen uniformly. p(x) = 1 / (b-a).

The requirement for choosing from a uniform distribution can be removed if this, more general, equation is used instead for the estimator:

General Version of Monte Carlo Estimator

This means that for each Xi chosen, you must divide by the probability density function at Xi. This equation change is necessary to do importance sampling, which significantly reduces variance over n samples.

Implementation Preparation

Before I started implementing Monte Carlo, I first needed to extend my current code to cast additional normal rays after the first initial hit. I added code in the closest hit shader that generates a new ray. This creates recursive calls to the closest hit shader that may recurse infinitely. To stop this, I created a terminating condition that if the depth is greater than a certain amount, new rays will not be generated. For each new set of rays created the depth is incremented and added to the ray's payload. The new ray that I generated in addition to the shadow ray, was not random but was instead the ray of perfect reflection from the incident ray and the normal. 

Additionally, I removed the light attenuation because naturally when we switch to Monte Carlo, surfaces that are farther away from light sources will have a decreased chance of generating rays that point to light sources; Thus, Monte Carlo will automatically account for light attenuation. I also added the cosine factor in the rendering equation which is the angle between the normal of the surface and the direction of light. This is the result of these changes: 
 

Another thing I needed to do is when I switch to Monte Carlo I can't use point lights anymore. If I am generating lights at random directions, the chance of it pointing to a point in space (the point light) is practically zero. Instead I needed to use an area light. For now I am using the emission field from Assimp to give me light for the light surface. I also realized I don't need to generated a shadow ray which I did last week. Instead I generate two rays for every hit. One to the light source and one in the direction of perfect reflection. I was then able to achieve this result:

Now the ray tracer is starting to look really good but we still don't have global illumination and soft shadows and all that jazz. Time to implement Monte Carlo.

Implementation

Implementing Monte Carlo I ran into a few issues. First off, hlsl doesn't have a random library so I used Microsoft's hlsli file in one of their samples. After that I needed to compute a bidirectional scattering distribution function (BSDF) for each surface. Instead of computing the BSDF for each material I currently hardcoded it to use a constant BSDF which maps to a 100% rough surface for now. This is just temporary and will be changed once the basic Monte Carlo estimator is functional. I lastly needed to compute a random direction in the hemisphere surrounding the surface normal of a hit surface. After making these additions I was able to produce this result:


which doesn't necessarily look right. Using this algorithm I would have expected grainy noise. In the result above there is streaks of black clumped together which indicates there is something wrong with my code. I unfortunately was not able to solve the problem this week but I will be fixing my code next week. What does look promising is the ceiling is showing hints of global illumination, where the left and right sides of the ceiling are red and green respectively. 

Up Next

In the next post, I will fix the Monte Carlo estimator and implement importance sampling to reduce the number of samples needed. Monte Carlo by itself is inefficient and if I choose rays with a higher probability of hitting light sources, I can create the image with less samples. Next week I will also investigate other methods to improve performance, and look into creating BSDFs from materials.

Comments

Popular posts from this blog

Rendering a Scene

Denoising

Enabling Raytracing - Part 2