Lab 03: BB-8

Goals

By the end of this lab, you will:

In this lab, you will render BB-8 using the shading techniques we introduced this week. The primary objective is to implement the Phong Reflection Model and add shadows to the scene. An optional part is to make BB-8's eyes look like a mirror.

To access the lab template, please click the link in the Lab 3 Assignment Link post on our Ed discussion board. When you preview the index.html page (via the Go Live button), you should see a blue background for the sky.

All of the code you will write will be in the renderer.js file which contains a few class definitions. In particular, there are class definitions for a Ray, Sphere, and the Renderer class. A lot of the setup will look similar to what we did last week in Lab 2.

The Renderer class takes in (1) the canvasId (string) of the canvas in which we would like to render our scene, (2) the vertical field-of-view (fov), the objects in the scene and the light information: (1) the ambient color ca, and (2) a light object which as a position and color cl. The objects are all spheres (even the ground) and stored in an array of length 6 (planet, body, head, 2 eyes, and a remote droid).

The Renderer class saves the incoming information (fov, canvas, objects, ca, light), which is then used in the render method to render the scene. Your job primarily consists of modifying the render method as well as the intersect method of the Sphere class.

Before getting started, please have a look at the way the materials are setup in the index.html file. There are 4 materials here for the ground, BB-8, eyes and remote. Each of these is described using JSON (similar to a dictionary/map) and contains fields for the different material properties. At the very least, each of these has a type and the base material albedo (km). The type is a string which is either diffuse, specular or mirror. For specular and mirror types, there is also a ks field for the specular reflection coefficient.

Part 1: Show that the reflection vector is a unit vector.

This is a theoretical question which can be done at any time. I would suggest doing this part after the lab if you want to focus on writing code during the lab.

I've suggested normalizing the normal $\vec{n}$ and direction to the light $\vec{l}$ a few times. When we compute $\vec{r} = -\vec{l} + 2 (\vec{n}\cdot\vec{l}) \vec{n}$, why don't we normalize $\vec{r}$? Please show that $\vec{r}$ is already a unit vector, provided $\vec{n}$ and $\vec{l}$ are unit vectors. In other words, we want to show $\lVert\vec{r}\rVert = 1$, which we can do by showing that $\lVert\vec{r}\rVert^2 = \vec{r}\cdot\vec{r} = 1$.

As in Lab 2, please upload a picture of your derivation to your repository, or type it in the README.md file using LaTeX.

Part 2: Create rays & intersect rays with spheres:

Part 2A: Although this will feel a bit repetitive, it's a good idea to revisit the techniques we used to (1) create rays and (2) intersect these rays with spheres. For each pixel (i, j), please create a ray that passes through the pixel starting from the eye. We are still using the eye as the point (0, 0, 0) in this lab.

Part 2B: Next, complete the intersect function in the Sphere class. You can copy over what you had for the sun from last week's lab (or from the exercise) to get started, but please note that this function now returns something different. In particular, we need to return information about the intersection point: the surface point at the intersection, the normal to the surface at this point and which object is intersected (so we can retrieve the material attribute).

Note that our Ray class has been extended with an evaluate function. Any point along the ray can be obtained as point = ray.evaluate(t) for some $t$ value and Ray object ray. You will need this to calculate the 3d coordinates of the intersection point. You will also need to calculate the normal vector to the surface (please see the notes on how to do this for a sphere).

Part 2C: Next, please complete the hit function of the Renderer class. This function will loop over all the objects in the scene (this.objects) and return the information about the closest intersection point. In other words, you should have a for loop over this.objects that finds the object with the minimum $t$ intersection value (if any) and returns information about the intersection (the same information returned by the Sphere intersect function: surface point, surface normal and object).

Please ensure the $t$ value for the intersection is greater than zero. It's usually a good idea to use a small tolerance (like 1e-5) but using 0 should be okay for this lab.

Part 3: Implement the Phong Reflection Model.

Please complete the computeColor function to account for the lighting sources and materials in the scene. You should model a single light (not a bidirectional light). When an intersection occurs, please add the ambient, $I_a$, diffuse ($I_d$) and specular ($I_s$) contributions (if necessary). Note that some materials are diffuse, and some are specular, which can be determined through the ixn.object.material.type property (check if this attribute is diffuse or specular). Materials that have the mirror type should also have the specular ($I_s$) contribution.

To make sure this part works with the previous part, I would suggest starting by only returning the ambient color term: $I_a = c_a k_m$. You should then see something similar to the image below.

Next add the diffuse and specular terms. Please use a shininess exponent of your choice and feel free to adjust some of the parameters in the material definitions (those in index.html).

Part 4: Add shadows.

Finally, please add shadows by checking if a shadowRay cast from the intersection point to the light.position hits an object in the scene and only returning the $I_a$ term in the computeColor function.

Part 5: Making BB-8 look more like BB-8 (optional, but only a few extra lines of code!).

There are two things we need to do to make BB-8 look more realistic:

  1. The sphere for BB-8's head should be cut off below some value. Technically, it should taper towards the body but we'll just cut off the sphere. There is a function that is added to the head object called cutoff (try to find this in index.html). In your Sphere intersect function please add the following line after finding tmin:
const point = ray.evaluate(tmin);
if (this.cutoff && this.cutoff(point)) return undefined;
  1. Instead of using a constant km for the head and body objects, let's make this a function. In fact, there is already a kmFunction defined for these two objects that will return orange or grey, depending on the input surface point. In your computeColor function, try using this variable km value:
let km = ixn.object.material.km;
if (ixn.object.kmFunction) km = ixn.object.kmFunction(ixn.p);

Part 6: Add mirrors (optional, but good practice for the lab next week).

This part is optional, but you will need to add mirror reflections next week, so it's good practice. Please see the pseudocode in the notes for a description of how to reflect an incoming ray off of a mirror-like surface. Then call the computeColor recursively with this new secondary ray. Whatever color is returned from the recursive call, I would suggest attenuating it by some factor (I used 0.25 below) before adding it to the final color. Try zooming in on the eyes to see the reflections of the planet and remote (look for the variable called zb in index.html and change this to -0.5).

Submission

The initial submission for the lab is due on Thursday 3/13 at 11:59pm EST. Please see the Setup page for instructions on how to commit and push your work to your GitHub repository and then submit your repository to Gradescope (in the Lab 3 assignment). I will then provide feedback within 1 week so you can edit your submission.


© Philip Caplan, 2025