Assignment 5: Photon Mapping

Due December 22, 2017 at 11:59pm on myCourses
Worth 20%


Overview

You will implement Photon Mapping in this assignment, a bidirectional density estimation method for computing biased (but consistent) solutions to the rendering equation. By the end of the assignment, you will be able to render radiometrically difficult lighting effects resulting from complex light sub-paths, like caustics, much more efficiently than with the unbiased Monte Carlo solutions you've implemented so far.


Task 1 : Photon Mapping (70 points)

Photon Mapping is a two-pass algorithm where photons are first emitted from light sources, scattered and deposited on surfaces in the scene; afterwards, the radiance is estimated by either approximating the local density of photons around a shading point or by using photons in the scene to approximate the incident radiance distribution about the shading point.

Recall the hemispherical form of the rendering equation discussed in class: \begin{equation} L(\mathbf{x}, \omega) = \int_{\mathcal{H}^2} f_r(\mathbf{x}, \omega', \omega) L(r(\mathbf{x}, \omega'), -\omega') \cos\theta' \, \mathrm{d}\omega', \label{path} \end{equation}

where $r(\mathbf{x}, \omega')$ is the ray tracing function that returns the nearest intersection point for a ray traced from the point $\mathbf{x}$ and in direction $\omega'$. We'll see that, in Photon Mapping, we can estimate this quantity by weighting the contribution of some number of photons $n$ (within an area $\Delta A$ around the shading point $\mathbf{x}$) by the BRDF $f_r$ as:

\begin{equation} L(\mathbf{x}, \omega) \approx \sum_{p=1}^{n} f_r(\mathbf{x}, \omega_p, \omega) \frac{\Delta \phi_p(\mathbf{x}, \omega_p)}{\Delta A} \end{equation}

For reference, the user-tweakable parameters in our photon mapper are defined according to the XML properties below:

<integrator type="photon-map">
        <int name="photonCount" value="50000"/>
        <float name="radius2" value="0.0008"/>
        <int name="samplesFG" value="100"/>
        <int name="samplesDI" value="50"/>
</integrator>

The meanings of these parameters, if not immediately obvious, will become so below.

Pass 1 : Generating the Photon Map (40 points)

An API template for the Photon Mapper is provided in src/integrators/pm.cpp. You will generate a photon map by completing the implementation of PhotonMappingIntegrator::generatePhotonMap() in order to emit, scatter and store photons from light sources into the scene. Note that src/integrators/pm.cpp already contains a Photon structure and an array of Photon where you can store your photons.

struct Photon {
    Point3f  x;   //Position of photon
    Vector3f w;   //Direction of photon
    Color3f  phi; //Power of the photon
    Normal3f n;   //Surface normal of the deposited photon
};
It is important to note the difference between photons stored in the photon map and photons emitted from light sources. For example, you must divide all the photons' power ($\phi$) by the number of emitted photons (assuming you perfectly importance sample their emission profile), not the number of stored photons.

Details

Tips & Tricks

Photon map (5000 photons)

Pass 2: Computing Outgoing Radiance with Density Estimation (30 points)

Implement a simple radiance estimate in PhotonMappingIntegrator::Li() using a disk lookup that's co-planar with the shading point, and with a fixed lookup radius m_radius2, and a constant density estimation kernel, as:

\begin{equation} L(\mathbf{x}, \omega) \approx \sum_{p=1}^{n} f_r(\mathbf{x}, \omega_p, \omega) \frac{\Delta \phi_p(\mathbf{x}, \omega_p)}{\Delta A} \approx \sum_{p=1}^{k} f_r(\mathbf{x}, \omega_p, \omega) \frac{\phi_p}{\pi r^2} \end{equation}

where $k$ is the number of photons inside the disk lookup region of radius $r$. As usual $f_r$ is the (view-evaluated) BRDF.

You will use a KD-Tree we provide for you in src/core/kdtree.h in order to find the $k$ photons that lie within the fixed search radius. You will need to understand how to use the functions KDTree::build(), KDTree::push_back() and KDTree::nnSearch(). Read the comments in src/core/kdtree.h and be careful to correctly handle all possible return values of the function KDTree::nnSearch().

The result of KDTree::nnSearch() will yield photons withing a spherical volume around your lookup point, however we need to only consider those photons that lie on the same surface as the shading point: make sure that photon normals ($N_p$) are incident to the surface normal ($N_p \cdot N > 0$) and make sure that photons are in front of the surface and not behind it ($N_p \cdot \omega_i > 0$ and $N \cdot \omega_i > 0$). Of course, as with any such floating point test, you should consider adding some meaningful epsilon value in order to avoid false negatives.

Note that the KD-tree can also be used to perform an adaptive radius lookup (where, instead of specifying the lookup radius, you specify the number of photons you want to find), however this assignment only requires you implement the basic fixed-radius density estimate. Adaptive radius density estimation is reserved as a hacker point task (see the course website).

100 000 photons
1 000 000 photons
50 000 000 photons

Once you have implemented both the photon map building and density estimation passes, render the scene scenes/hw5/Tests/cbox-pm.xml and compare the result with your path tracer. Both renders should be nearly identical with about 50M photons and a squared fixed radius lookup of 0.0008.

Tips & Tricks


Task 2: Photon Mapping only for indirect illumination (10 points)

Now that you have a basic photon mapper implemented, we'll move towards a more efficient second pass. Modify your integrator to decouple the computation of direct and indirect illumination, as follows: only evaluate indirect illumination using the photon map, and evaluate direct illumination using your old Direct Integrator.

Here, you need to be careful to exclude any photons that contribute direct illumination to the photon map, during photon map construction. These are the photons that scatter immediately (i.e., one event) after emission. Not doing so would result in a rendering that double counts direct illumination!
Photon Mapping (1M photons, $r^2=0.0008$, samplesDI=0)
Photon Mapping with DI (1M photons, $r^2=0.0008$, samplesDI=50)

Task 3: Photon Mapping with Final Gathering (20 points)

In Task 2 you used direct density estimation (i.e., at the shading point) to compute the indirect illumination contribution. Alternatively, you can use a final gathering (FG) pass to improve the convergence/quality of the indirect illumination. Final gathering operates similarly to the Direct Integrator, however now when computing the outgoing (indirect) radiance you will use a density estimate with the photons in your modified photon map to estimate the incident illumination distribution at your shading point.

It is important to realize that, with final gathering (unlike in Task 2), you still need to store the photons that contribute to direct illumination in your photon map, even though you are using the Direct Integrator to compute the direct illumination: since you're integrating these "direct photons" one bounce away from your shading point during the final gather, they contribute to the 1-bounce indirect illumination now (similarly to virtual point lighting).
Don't forget to add the direct illumination to the indirect illumination to get the final radiance.

Final gathering should significantly improve the visual convergence of your photon mapping integrator.

Photon Mapping (50k photons, $r^2=0.008$, samplesDI=20, samplesFG=400)
Path Tracing (512 spp)

At about equal render time, and in the scenes we provide you with, your photon mapper should generate a much more visually pleasing image when compared to path tracing. Do recall, however, that the photon mapping algorithms you implement above are all biased estimator and, as such, they will not converge to exactly the same image as a converged path tracing rendering.


What to Submit

Finished? Render the scenes contained in scenes/hw5/Final and use the given script to submit your files (as you did for the previous assignments). Include any specific comments in the appropriate section of the script.