Starter Code

Ray tracing assignment

In this assignment we are going to be adding two new features to the ray tracing system the authors have provided in chapter 16. The first feature is the addition of a new cylinder shape. The second feature is a more accurate model for the ambient lighting contribution.

Start project

Click the button above to download the starter files for this project.

Running this project should produce the image below.

This scene includes two gray boxes that form the back wall and roof of a structure, a checkered ground plane, and a "sky sphere" formed by a large blue sphere that encloses the entire scene.

Adding columns

Next, we are going to add two columns to the structure.

To implement columns in your program you should start by adding a data structure and a global array to represent the columns:

struct Cylinder
{
  float x; // x coordinate of base point
  float z; // z coordinate of base point
  float top; // y coordinate of top of cylinder
  float bottom; // y coordinate of bottom of cylinder
  float r; // cylinder radius
};

Cylinder cylinders[2] = Cylinder[2](Cylinder(-2.0,-0.5,3.0,0.0,0.5),Cylinder(2.0,-0.5,3.0,0.0,0.5));

You will need to add a function

Collision intersect_cylinder_object(Ray r,int index)

to your program that check to whether or not a given ray has collided with one of the two cylinders.

To check for a collision between a ray with origin

and direction

with a cylinder with a base point located at

and base radius r you will need to solve the equation

(px + dx t - cx)2 + (pz + dz t - cz)2 = r2

for t. If the equation has complex roots there will be no collision. If the equation has two real roots, you should select the real root with the smallest positive value as the collision time t. You then need to check whether the y coordinate of the collision point

y = py + dy t

falls between y = bottom and y = top.

After constructing the collision function for cylinders you will also need to update the get_closest_collision() function because you now have more objects in the scene.

Better ambient lighting

The final step in this assignment is to replace the ambient part of the lighting model with a more accurate ambient lighting computation.

The original lighting model operates on the assumption that every point in the scene will receive some ambient light, regardless of where that point is located. In this version of the program we are going to replace that simplistic assumption with a more realistic model of ambient light, called global illumination.

In this new model each point in the scene receives an ambient lighting contribution based on how much of the sky the point can see. To estimate what fraction of the sky that point is exposed to we will shoot 32 randomly chosen rays from that point and count what fraction of the rays can get to the sky sphere without first running into some other object in the scene.

The result will be a scene that looks like this.

The darkest shadows in this version are on the back wall right underneath the roof. Those points don't get any illumination from the point light source, and also get hardly any ambient illumination because they can't see very much of the sky. The shadows cast by the columns get slightly lighter as you travel toward the floor, since those points will see relatively more of the sky as you travel down from the roof. Finally, the points on the back wall closer to the sides will appear slightly brighter than the points in the center of the back wall, because those points see relatively more sky.

To implement this improved lighting model we will need to start with an algorithm that can generate random directions for rays. Here is some code you can use for this purpose.

//------------------------------------------------------------------------------
// Random number generator
// From: https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/
//------------------------------------------------------------------------------
uint rng_state  = gl_GlobalInvocationID.x*521+gl_GlobalInvocationID.y;

float rand_pcg()
{
    uint state = rng_state;
    rng_state = rng_state * 747796405u + 2891336453u;
    uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
    return (((word >> 22u) ^ word)%65536u)/65536.0;
}

vec3 rand_vec3()
{
  vec3 result = vec3(rand_pcg(),rand_pcg(),rand_pcg());
  if(result.y < 0.0)
    result.y = -result.y;
  return normalize(result);
}

You will use this code to compute your ambient lighting contribution in the function that computes the lighting. Do the following:

  1. Set up a loop that generates 32 random ray directions for a ray whose starting point is c.p + c.n * 0.01.
  2. For each ray determine whether or not the ray can reach the sky without colliding with some other object first.
  3. If the ray can reach the sky, add a contribution worldAmb_ambient * objMat_ambient * max(cos_theta, 0.0) to the ambient lighting term, where cos_theta is dot product between the ray direction and c.n.
  4. To compensate for the fact that you sampled 32 rays, divide the ambient contribution by 32 after the loop.

Finally, to get a better balance between ambient and point light sources, change the lighting and material constants in the program to this:

vec4 worldAmb_ambient = vec4(1.0);

vec4 objMat_ambient = vec4(0.4, 0.4, 0.4, 1.0);
vec4 objMat_diffuse = vec4(0.6, 0.6, 0.6, 1.0);
vec4 objMat_specular = vec4(1.0, 1.0, 1.0, 1.0);
float objMat_shininess = 50.0;

vec3 pointLight_position = vec3(-4.0,5.0, 6.0);
vec4 pointLight_diffuse = vec4(0.8, 0.8, 0.8, 1.0);
vec4 pointLight_specular = vec4(1.0, 1.0, 1.0, 1.0);