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

## Introduction

In this task you will implement a new type of light source: an infinite environment light. An environment light is a light that supplies incident radiance from all directions on the sphere. The source is thought to be "infinitely far away" and is representative of realistic lighting environments in the real world. As a result, rendering with environment lighting can be quite striking.

The intensity of incoming light from each direction is defined by a texture map parameterized by phi and theta, as shown below.

You'll be implementing the `EnvironmentLight::sample_L()`

method in *static_scene/environment_light.cpp*. Start with uniform direction sampling to get things working, then move to a more advanced implementation that uses importance sampling to significantly reduce variance in rendered images.

We recommend testing using the unlit bunny pedestal scenes to better see the effect of your environment map on the illumination. These are *bunny_unlit.dae* and *bunny_microfacet_cu_unlit.dae*. Don't test on Cornell box scenes since the object won't be exposed to the environment.

Also, neither the uniform hemisphere sampling from Project 3-1's `PathTracer::estimate_direct_lighting_hemisphere`

nor the delta BSDFs from Part 1 will work, as environment lights are like point lights in that they do not have a primitive in our scene and cannot be intersected by a ray.

Your image may have a couple white specks on it like the reference image above. If you're using the staff library you can try switching to your own Project 3-1 code and see if the problem persists, or implement some sort of filtering on the incoming spectrums. Neither of these is required for this part of the project, and should not affect scenes without environment lighting.

## Task 0: Refactoring 3-1 Code

In `est_radiance_global_illumination`

, if no intersection with the scene occurs and there is an environment map, we want to then render from the environment map. If `envLight`

is non-null, we return the result of calling `sample_dir`

with the view ray. Otherwise there is no environment light, so we return the empty spectrum as before.

**If you're using your code from 3-1**, you'll need to make the following change in `PathTracer::est_radiance_global_illumination`

. It should look something like this, depending on your variable names and implementation.

```
if (!bvh->intersect(r, &isect))
return envLight ? envLight->sample_dir(r) : L_out;
```

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

## Task 1: `EnvironmentLight::sample_dir()`

Fill in this function. Use the helpers `dir_to_theta_phi`

and `theta_phi_to_xy`

to get coordinates that can be used to index into the `envMap`

variable, then use bilinear interpolation to sample a `Spectrum`

from `envMap`

at that location.

**How to get .exr files:** High dynamic range environment maps can be large files, so we have not included them in the starter code repo. You can download a set of environment maps here.

After this, you should see an environment map in the background of your scene when you use the `-e`

command line flag to load an environment map, as in:

```
./pathtracer -t 8 -e ../exr/grace.exr ../dae/sky/bunny_unlit.dae
```

## Task 2: Uniform sampling

To get things working, your first implementation of `EnvironmentLight::sample_L()`

will be quite simple. You should generate a random direction on the sphere (with uniform $\frac{1}{4\pi}$ probability with respect to solid angle) using the `sampler_uniform_sphere`

. Convert this direction to coordinates $(\phi, \theta)$, then look up the appropriate radiance value in the texture map using bilinear interpolation. Don't forget to set the distance to the light to `INF_D`

and the `pdf`

to $\frac{1}{4\pi}$.

Tips:

`envMap->data`

contains the pixels of the environment map.- The size of the environment texture is given by
`envMap->w`

and`envMap->h`

. - We have provided a few helper functions such as
`dir_to_theta_phi()`

that will be useful here.

## Task 3: Importance sampling

Much like light in the real world, most of the energy provided by an environment light source is concentrated in the directions toward bright light sources. Therefore, it makes sense to bias selection of sampled directions towards the directions for which incoming radiance is the greatest. In this task, you will implement an importance sampling scheme for environment lights. For environment lights with large variation in incoming light intensities, good importance sampling will significantly decrease the noise of your renderings.

The basic idea is that you will assign a probability to each pixel in the environment map based on the total flux passing through the solid angle it represents. This will be our strategy for sampling the environment map based distribution:

- Sample a row of the environment map using the marginal distribution $p(y)$.
- Sample a pixel within that row using the condition distribution $p(x|y)$.
- Convert that $(x,y)$ to a direction vector and return the appropriate radiance and pdf values.

You will be able to use the *probability_debug.png* image that is saved out by `save_probability_debug()`

to debug the correctness of your marginal and conditional distributions. For example, you should get something like this when loading the *field.exr* map after implementing steps 2-3 (we provide you with step 1 to compute the pdf):

### Step 1: Compute the pdf

As a free gift, we provide most of this part of the code to you in `EnvironmentLight::init()`

.

In order to compute the pdf, we need to convert the environment map into a 2D probability density function on the domain $[0,w]\times[0,h]$. We will get samples $(i,j)$ from this distribution that we will map to a direction vector with spherical coordinates $(\theta,\phi) = (\pi \frac j h, 2\pi \frac i w)$.

This pdf will have to satisfy the property

$\int_0^w \int_0^h p(x,y) dx dy = 1$.

Our pdf will be piecewise constant on each rectangle of the form $[i,i+1] \times [j,j+1]$. We can break our integral up into a sum:

$\int_0^w \int_0^h p(x,y) dx dy = \sum_{i=0}^{w-1} \sum_{j=0}^{h-1}p(i,j) \int_{j}^{j+1} \int_{i }^{i+1} dx dy = \sum_{i=0}^{w-1} \sum_{j=0}^{h-1} p(i,j).$

Thus, we want to satisfy the constraint $\sum_{i=0}^{w-1} \sum_{j=0}^{h-1} p(i/w,j/h) = 1$. If the environment map $E$ were being pasted onto a flat texture, we might want to do something like $p(i,j) \propto E[i,j]$ (the $\propto$ symbol means "proportional to"). But since we are pasting $E$ onto a sphere, we want to create a density such that a uniform environment map would result in uniform sphere sampling. This means we need to take $p(i,j) \propto E[i,j] \sin \theta$ for $\theta=\pi \frac j h$. This will give us a base case of uniform spherical sampling.

Our three constraints (piecewise constant, sums to 1, proportional to sine-weighted environment map) combine to give us a pdf:

$p(x,y) = \frac{E[\lfloor x \rfloor,\lfloor y \rfloor] \sin (\pi \lfloor y \rfloor/h)}{\sum_{i',j'} E[i',j'] \sin (\pi j'/h)}.$

Most of the code for this part has already been provided. The variable `pdf_envmap`

in the function `EnvironmentLight::init()`

given holds the weights for each pixel -- **you must first normalize pdf_envmap so that it sums to 1 as desired**. As usual, we use typical row-major 2D indexing where the 2D index `[i,j]`

corresponds to 1D index `[j*w+i]`

.

### Step 2: Compute the marginal distribution

Your task starts from here: step 2.

Remember that the marginal density $p(y)$ is defined as $p(y) = \int_0^w p(x,y) dx = \sum_{i=0}^{w-1} p(i,\lfloor y \rfloor).$ This density will be piecewise constant. In order to sample from the marginal distribution, we actually want its cumulative distribution function, which we will store as an array. In this step you should calculate the cumulative marginal distribution

$F(j) = \sum_{j'=0}^j \sum_{i=0}^{w-1} p(i,j')$

and store it in the 1D array `marginal_y`

.

### Step 3: Compute the conditional distributions

Remember that the conditional density $p(x|y)$ is equal to $\frac{p(x,y)}{p(y)}$ by Bayes' rule. The cumulative conditional distribution function is equal to

$F(i|j) = P(x < i | y = j) = \int_0^i p(x|y=j) dx = \int_0^i \frac{p(x,j)}{p(j)} dx = \sum_{i'=0}^{i-1} \frac{p(i',j)}{p(j)}.$

Remember that `marginal_y`

does *not* store the marginal density $p(j)$ but rather the marginal distribution $F(j)$, so you can't directly use it in the denominator for your calculations here. However, you probably can structure your loops for the first three steps in a way that reduces computation a bit. (Don't worry too much about it, it's a trivial amount of computation compared to tracing rays!)

Store the conditional distributions in row major form in the array `conds_y`

, so that the distribution for $F(\cdot | j)$ is stored from `conds_y+j*w`

to `conds_y+(j+1)*w`

in memory.

### Step 4: Update `sample_L`

to do importance sampling

- Get a uniform 2D sample on $[0,1]^2$ from
`sampler_uniform2d`

. - Use the inversion method combined with your marginal and conditional distributions plus the
`std::upper_bound()`

function in order to get $(x,y)$ sampled from the pdf you calculated in Step 1. - Convert these $(x,y)$ values to a direction using our helper functions and store it in
`*wi`

. Set the distance to light to`INF_D`

. - Calculate the
`pdf`

value to be returned by querying the`pdf_envmap`

at your (integer valued) point $(x,y)$, which you will also multiply by $\frac{wh}{2\pi^2 \sin \theta}$. - Return the environment map value at $(x, y)$

The additional multiplicative factor in step 4 comes from the switch between probability densities. Switching from the environment map's domain $[0,w]\times [0,h]$ to the $(\theta,\phi)$ domain $[0,\pi]\times [0,2\pi]$ provides the $\frac{wh}{2\pi^2}$ term (the ratio of the domain's areas). Switching from spherical $(\theta,\phi)$ sampling to solid angle $d\omega$ sampling provides the $\frac{1}{\sin\theta}$ term (remember from class that $\sin \theta d\theta d\phi = d\omega$).

## This Part's Writeup

Pick one *.exr* file to use for all subparts here. Include a converted *.jpg* of it in your website so we know what map you are using.

- In a few sentences, explain the ideas behind environment lighting (i.e. why we do it/how it works).
- Show the
*probability_debug.png*file for the*.exr*file you are using, generated using the`save_probability_debug()`

helper function after initializing your probability distributions. - Use the
`bunny_unlit.dae`

scene and your environment map*.exr*file and render two pictures, one with uniform sampling and one with importance sampling. Use 4 samples per pixel and 64 samples per light in each. Compare noise levels. - Use the
`bunny_microfacet_cu_unlit.dae`

and your environment map*.exr*file and render two pictures, one with uniform sampling and one with importance sampling. Use 4 samples per pixel and 64 samples per light in each. Compare noise levels.