Final Project - Math 155B - Student: Cameron Chrisman

Overview

For my final project I decided to implement a kd-tree. A k-d tree is one of the most popular acceleration structures for raytracing, and can achieve very good results. k-d trees are in the same category of space partitioning algorithms as Octrees and BSP trees. What distinguishes a k-d tree is that space is always divided along a plane that is axis aligned (like Octrees), but each k-d tree node has exactly two children (like BSP trees). A large body of research in computer graphics has been devoted both to k-d tree construction and traversal. For more information see the references below.

I augmented my own raytracer to incorporate the k-d tree, and load a Wavefront .obj file for rendering triangle meshes. Below is a screenshot of a rendering of the Stanford Bunny. It contains approximately 69,000 triangles and would be nigh impossible to render in a realistic timeframe without acceleration.

Building the Tree

In implementing my k-d tree, I experimented with several different methods for constructing the tree structure. Each node consists of an axis aligned box, whose child nodes are defined by some partitioning of the box. The structure of the tree is mainly determined by the position of the splitting plane for each node. the splitting axis is determined simply by splitting along the longest axis.

The location along that axis, however, is more involved to compute. The first heurstic I tried is the spatial median of the node (i.e. boxes are just split in half). The next heuristic I experimented with was the median of all primitive coordinates. To calculate this, the key points for each primitive are calculated along the splitting axis. These points are determined by the minimum and maximum values of an axis aligned box bounding the primitive. The median of these coordinates is then used as the splitting position. This ensures that the split is along a primitive boundary, since it is not advantageous to have primitives crossing the splitting plane, and therefore being present in both child nodes. In practice this yields reasonable results, with an even split in the number of nodes in each child.

Traversing the Tree

The other major component of a k-d tree for raytracing is the traversal algorithm. I used the algorithm developed by Havran in his Phd. thesis in 2000 (see below). In practice this works well and doesn't suffer from problems handling difficult cases, and results in a relatively simple implementation.

Optimizations

A more sophisticated method for determining the splitting plane position was presented by Macdonald and Booth in 1990. MacDonald and Booth develop a heuristic for the resultant cost of a splitting position based on the surface area of, and number of objects contained in each child node. The splitting plane position that minimizes this cost function is chosen.

Implementing this algorithm revealed that finding the optimal split plane position tends to be more computationally expensive than the raytracing itself (at least for non-distributed large polygon scenes). The problem is that for each node, one must find enumerate all possible splitting positions. In their paper, MacDonald and Booth present a technique to reduce this space by reasoning that the split that minimizes the cost function must lie between the spatial median and the "object median", the object median is the split which equalizes the number of nodes each child node. Using this technique can reduce the number of splits to check considerably, however, a large amount of time can still be spent constructing the tree.

To help remedy this discrepancy between tree traversal time and rendering time, I made two simplifications. First, instead of using the object median as MacDonald and Booth suggest, I use the median of object vertex positions as described above. Second, I stochastically sample the interval between the spatial median and my object median by randomly skipping a split candidate with a probability proportional to the total number of splitting candidates (at some size threshold all objects are searched and the optimal split is found). This allows a single parameter to balance the time spent building the tree and its closeness to the optimal heuristic tree. Since this optimality is based on a heuristic, this is reasonable, and in practice yields good results.

A secondary optimization that I was going to add was Plücker coordinate triangle intersection, but I ended up getting more interested in k-d tree cost function heuristics, so my project ended up focusing more on that.

Screen Shots


Here is the bunny rendered with my k-d tree. I used a relatively high sampling rate to illustrate the resulting trade-offs between rendering time and tree-building time. Here the tree took, approximately 16s to build, and the scene rendered in 4.3s. When the sample rate is significantly reduced, building the tree takes 1.7s and rendering 5.7s. Increasing the sampling rate significantly, rendering times dropped to as low as 3.8s.

It is important to keep in mind that very little low level optimization is being done, the k-d tree nodes are not compact, there is a reasonable amount of memory redundancy. Also the maximum depth and number of objects per node was not tweaked for the scene. It is very reasonable to assume that with further optimization, frame rates greater than one will be achievable.

References:

@article{87856,
author = {David J. MacDonald and Kellogg S. Booth},
title = {Heuristics for ray tracing using space subdivision},
journal = {Visual Computer},
volume = {6},
number = {3},
year = {1990},
issn = {0178-2789},
pages = {153--166},
publisher = {Springer-Verlag New York, Inc.},
}

@PhdThesis{Havran2000:PhD,
author = "Vlastimil Havran",
title  = "Heuristic Ray Shooting Algorithms",
school = "Department of Computer Science and Engineering,
Faculty of Electrical Engineering,
Czech Technical University in Prague",
type   = "Ph.D. Thesis",
year   = "2000",
month  = "November",
url    = "http://www.cgg.cvut.cz/~havran/phdthesis.html"
}