This assignment will build your assignment 2 ray tracing code (or mine, if yours was not working) adding a kd-tree acceleration structure to make the ray tracing faster.


kd tree A kd-tree is data structure that divides space into regions separated by axis-aligned planes. This can significantly accelerate a ray tracer by avoiding ray intersection tests for objects in regions the ray cannot hit, and stopping the ray testing early by not doing ray-object intersections in regions beyond the first hit. The data structure is a binary tree, where each interior node is a split at a particular location on either the X, Y, or Z axis. Interior nodes contain a list of objects split by that splitting plane, with subtrees for objects that are entirely on one side or the other side of the plane. To locate a point in the tree, traverse down the tree, comparing the point to the splitting plane at each interior node to decide which branch to follow, until you reach a leaf.

With axis aligned planes, you can just subtract the x, y, or z coordinates to compute the distance from the plane or what side an object is on. For example, for a sphere of radius \(r\) with center \( (x_c, y_c, z_c ) \) and an X splitting plane through \(x=x_p\), the distance of the sphere center from the plane is \(\left|x_c - x_p\right|\). You can tell if a sphere intersects the plane if \(\left|x_c - x_p\right| \lt r\). If the distance is greater than the radius, then the sign of \(x_c-x_p\) tells you which subtree to use.

Unless you are doing the extra credit, use the longest-axis heuristic to build the tree. For all primitives in a node, find the minimum and maximum x, y, and z, and split in half along the axis with the greatest range. For spheres, these ranges are: \[ \begin{matrix} \max(x_i+r_i) - \min(x_i-r_i)\\ \max(y_i+r_i) - \min(y_i-r_i)\\ \max(z_i+r_i) - \min(z_i-r_i) \end{matrix} \]

The ray has a start point, \(\vec{e}\), direction, \(\vec{d}\), closest distance along the ray, \(n\) (initially \(\mathit{near}\) or \(\epsilon\)), and farthest distance along the ray, \(f\) (initially \(\infty\)). The first possible intersection point along the ray is at \(\vec{p}=\vec{e} + n\vec{d}\). As you traverse the kd-tree data structure, \(f\) will be updated to the closest intersection point found so far, and \(n\) and \(\vec{p}\) will be updated to just past the last splitting plane.

To use the kd-tree to find the closest intersection, start by recursively traversing down the tree to find the node containing the initial \(\vec{p}\). Test against any objects in interior nodes on the traversal down, updating \(f\) and remembering the intersected object if necessary. Then intersect the ray against any objects in the node containing \(\vec{p}\), again updating \(f\) to the closest intersection so far.

Return in the parent node to find the intersection of the ray with the splitting plane (as a ray/plane intersection). If \(n < t < f\) update \(n=t\) and \(\vec{p}=\vec{e}+n\vec{d}\), then traverse down the other branch of the tree. If \(t < n\), the intersection with the splitting plane is behind the ray, so there's no need to traverse the other branch. If \(t > f\), there's already an intersection closer than anything you could find on the other branch.

In pseudo-code:

  ray = (e: start point, d: direction, n: epsilon, f: infinity)
  closest = null
  kdTraverse(kdRoot, ray.e + ray.n * ray.d)
  return closest

kdTraverse(kdNode, p)
  if (kdNode = null) return
  (t, object) = intersect(ray, kdNode.objects)
  if (ray.n < t < ray.f)
    ray.f = t
    closest = object
  if (p[kdNode.axis] < kdNode.split)
    kdTraverse(kdNode.left, p)
    t = planeIntersect(ray, kdNode)
    if (ray.n < t < ray.f)
      kdTraverse(kdNode.right, ray.e + t * ray.d)
    kdTraverse(kdNode.right, p)
    t = planeIntersect(ray, kdNode)
    if (ray.n < t < ray.f)
      kdTraverse(kdNode.left, ray.e + t * ray.d) 

634 only

Students taking this class as CMSC 435 need only handle spheres (ignore any non-sphere objects). Students taking the class as CMSC 634 should handle polygons as well.

Extra credit

There are up to 25 points of extra credit possible for this assignment. There are two independent components to the extra credit, and you can attempt either or both. Extra credit is only available if submitted without a late penalty (either submitted on time or using free late days).

For up to 10 points, construct your kd-tree in a preprocess. Design your own file format to save the kd-tree, and load the processed file into your ray tracer where you use it for rendering. There are a few options when saving data structures (like a tree) that depend on pointers, since data will almost certainly not be re-allocated at the same pointer locations. You can write out nodes in a depth-first traversal, so they can be reconstructed and reconnected back into a tree when loading. You can allocate nodes in an array and use array indices instead of pointers, which will remain valid between programs. Finally, you can treat the pointers as a form of label, and construct an associative array to map from original pointer to new.

For up to 15 points of extra credit, use the surface-area heuristic for tree construction instead of the longest-axis heuristic. For the surface-area heuristic, you consider each node to bound a rectangular volume of space just big enough to contain all of the objects inside. The root node bounds a volume given by the minimum and maximum x, y, and z of all of the objects. At each possible split, you choose the one with the minimum cost, with a cost function that counts the objects that cross the splitting plane (since those must be tested for either subtree) plus a fraction of the objects in each subtree, weighted by the ratio of the surface area nodes: \[ \mathit{crossingObjectCount} + \mathit{leftObjectCount} * \mathit{leftArea}\ / \mathit{parentArea} + \mathit{rightObjectCount} * \mathit{rightArea}\ / \mathit{parentArea} \]

For split plane location, consider the extreme points of each object (e.g. \(x-r\) or \(x+r\) of each sphere in the node for splits in the x direction).

What to turn in

Turn in this assignment electronically by pushing your source code to your class git repository by 11:59 PM on the day of the deadline and tagging the commit assn6. Do your development in the trace directory so we can find it.

Also include an assn6.txt file at the top level of your repository telling us about your assignment. Tell us what works, and what does not. Also tell us what (if any) help you received from books, web sites, or people other than the instructor and TA. Finally, if you did anything toward the extra credit, be sure to tell us about it in this file.

You must make multiple commits along the way with useful checkin messages. We will be looking at your development process, so a complete and perfectly working assignment submitted in a single checkin one minute before the deadline will NOT get full credit. Individual checkins of complete files one at a time will not count as incremental commits. Do be sure to check in all of your source code, but no build files, log files, generated images, zip files, libraries, or other non-code content.