*This image took about two minutes seconds to render. It has 256 samples/pixel, 4 samples/light, and max ray depth equal to 7.*

## Introduction

In this part, you're going to implement mirror and glass models with both reflection and refraction.

After completing this part, make sure you can render

*CBdragon.dae*(mirror only)*CBlucy.dae*(glass only)*CBspheres.dae*(mirror and glass)

## Task 0: Refactoring 3-1 Code

To ensure that we handle emitted light in the scene correctly with delta bsdf's we will need to modify the `at_least_one_bounce_radiance`

function.

If the current intersection is a delta bsdf, we do not want to include the radiance from direct lighting as it will not be reflected along the view vector.

Instead, we take the emission from the next intersection (using a call to `zero_bounce_radiance`

) and add it to our incoming radiance from our recursive call. The sum of these two values then is our incoming radiance which we scale with the bsdf and Lambert's Cosine Law as before.

We also make the assumption that both incident and outgoing vectors at our intersection point away from the point of interest.

**If you're using your code from 3-1,** change `at_least_one_bounce_radiance`

to only include `one_bounce_radiance`

if the current intersection doesnt have a delta bsdf.

```
if (!isect.bsdf->is_delta())
L_out += one_bounce_radiance(r, isect);
```

Additionally, if the current intersection has a delta bsdf, add the `zero_bounce_radiance`

of the new shadow ray/intersection to the recursive `one_bounce_radiance`

, before scaling both the same way as before.

```
Spectrum L = at_least_one_bounce_radiance(rec, new_isect);
if (isect.bsdf->is_delta())
L += zero_bounce_radiance(rec, new_isect);
```

Finally, ensure you are using the absolute value of the cosine term (i.e. `abs_cos_theta`

or `fabs(w_in.z)`

), since we assume the vectors all point away from the intersection.

**If you're using the staff library**, these changes have already been made for you.

## Task 1: Reflection

Implement the `BSDF::reflect()`

function.

We recommend taking advantage of the object coordinate space that BSDF calculations occur in. This means that the origin is the intersection point and the $z$ axis lies along the normal vector. In this situation, reflection should be a trivial one line transformation of the $x,y,$ and $z$ coordinates.

## Task 2: Mirror Material

Implement `MirrorBSDF::sample_f()`

. This should be straightforward if you rely on your `BSDF::reflect()`

function. Remember that delta BSDFs like this one are a special case in `PathTracer`

's lighting estimation routines, so you should set `pdf`

equal to 1 in `sample_f()`

. *Note* that you must actually return `reflectance / abs_cos_theta(*wi)`

because we need to *cancel out* the cosine that the `at_least_one_bounce_radiance`

function will multiply by. Perfect mirrors only change the ray direction, they don't cause any Lambertian falloff.

Note that the `MirrorBSDF::f()`

implemented for you returns an empty `Spectrum()`

since we assume that a `wi`

direction that was not created using `sample_f()`

has zero probability of being equal to the reflection of `wo`

. (This is a convoluted way of ensuring that we never double count our radiance with delta BSDFs, since we have the special `is_delta()`

.)

Note: make sure you run with a maximum ray depth greater than 1, otherwise the spheres won't show up. Why is that the case?

## Task 3: Refraction

To see pretty glass objects in your scenes, you'll first need to implement the helper function `BSDF::refract()`

that takes a `wo`

direction and returns the `wi`

direction that results from refraction.

As in the reflection section, we can take advantage of the fact that our BSDF calculations always take place in a canonical "object coordinate frame" where the z axis points along the surface normal. This allows us to take advantage of the spherical coordinate Snell's equations. In other words, our `wo`

vector starts out with coordinates:

$\omega_o.x = \sin \theta \cos \phi, \quad \omega_o.y = \sin \theta \sin \phi, \quad \omega_o.z = \pm \cos \theta.$

Note: we put a $\pm$ sign on the $z$ coordinate because when `wo`

starts out *inside* the object with index of refraction greater than 0, its $z$ coordinate will be negative. The surface normal always points out into air. When $z$ is positive, we say that we are entering the non-air material, else we are exiting. This is depicted in the figure.

For the transmitted ray, $\phi' =\phi + \pi$, so $\cos \phi' = -\cos \phi$ and $\sin \phi' = -\sin \phi$. As seen in the figure, define $\eta$ to be the ratio of old index of refraction to new index of refraction. Then Snell's law states that $\sin \theta' = \eta \sin \theta$. This implies that $\cos\theta' = \sqrt{1-\sin^2 \theta'}= \sqrt{1-\eta^2 \sin^2 \theta} = \sqrt{1-\eta^2(1-\cos^2\theta)}$.

In the case where $1-\eta^2(1-\cos^2\theta) < 0$, we have total internal reflection--in this case you return false and the `wi`

field is unused.

**Be careful when implementing this in code**, since you are only provided with a single `ior`

value in the `refract`

function: when entering, this is the new index of refraction of the material $\omega_i$ is pointing to, else it is the old index of refraction of the material that $\omega_o$ itself is inside. In other words, $\eta = 1/$`ior`

when entering and $\eta =$`ior`

when exiting.

In spherical coordinates, our equations for $\phi'$ and $\theta'$ tell us that

$\omega_i.x = -\eta \omega_o.x, \quad\omega_i.y = -\eta \omega_o.y, ,\quad \omega_i.z = \mp \sqrt{1-\eta^2(1-\omega_o.z^2)},$

where we are indicating that $\omega_i.z$ has the opposite sign of $\omega_o.z$.

## Task 4: Glass Material

Now you can implement `GlassBSDF::sample_f()`

. As with reflection, `GlassBSDF::f()`

simply returns `Spectrum()`

.

For sampling the glass BSDF: when `wo`

has a valid refracted `wi`

(so total internal reflection does not occur), both reflection *and* refraction will occur at this point. To model this, we need to know the ratio of the reflection energy to the refraction energy. The Fresnel equations model the actual physics behind this phenomenon. However, we can simply use Schlick's approximation to decide the ratio in this assignment, since it is much easier to evaluate. Since `sample_f()`

is only allowed to return one ray direction, we will use Schlick's approximation to give us a coin-flip probability of either reflecting *or* refracting. Thus, our `sample_f()`

routine should look like the following:

- If there is total internal reflection
- assign the reflection of
`wo`

to`*wi`

- set the
`*pdf`

to 1 - return
`reflectance / abs_cos_theta(*wi)`

- assign the reflection of
- Else
- Compute Schlick's reflection coefficient $R$
- If
`coin_flip(R)`

- assign the reflection of
`wo`

to`*wi`

- set the
`*pdf`

to`R`

- return
`R * reflectance / abs_cos_theta(*wi)`

- assign the reflection of
- Else
- assign the refraction of
`wo`

to`*wi`

- set the
`*pdf`

to`1-R`

~~return~~`(1-R) * transmittance / abs_cos_theta(*wi) / eta^2`

(where`eta^2`

is the same as in the refraction function)- return
`(1-R) * transmittance / abs_cos_theta(*wi) * eta^2`

(where`eta^2`

is the same as in the refraction function)

- assign the refraction of

The `eta^2`

term in the case of refraction is necessary because radiance concentrates when a ray enters a high index of refraction material (low `eta`

) and disperses when a ray exits such a material (high `eta`

). The `abs_cos_theta(*wi)`

terms are present to cancel out the cosine term in `at_least_one_bounce_radiance`

, just like in the mirror BSDF case.

## This Part's Writeup

- Show a sequence of six images of scene
`CBspheres.dae`

rendered with`max_ray_depth`

set to 0, 1, 2, 3, 4, 5, and 100. The other settings should be at least 64 samples per pixel and 4 samples per light. Point out the new multibounce effects that appear in each image. Explain how these bounce numbers relate to the particular effects that appear.