Assignment 2 Lazy KD-Tree
Alex Li
Date submitted: 26 Apr 2006
Code emailed: 26 Apr 2006
Description of implementation approach and comments
I based my approach on the suggestions in the assignment handout. The first optimization I tried was the lazy construction of the kdtree. I initially build out the tree to a certain depth, and mark all unfinished nodes as "lazy". Then when we do the ray traversal, when a ray hits a lazy node, I determine whether it should be a leaf or interior and build the node accordingly. If it is an interior node, the split is found and two lazy children are created below it. The traversal code than proceeds the same as the old kdtree code (handling the newly minted leaf or interior node just as it would in a normally built kdtree). I tested with different initial build depths -- it turns out that whether building out to 5 initially or not at all results in about the same performance. In the end, I opted to be super lazy, and not build out the tree initally at all.
I ran into some trouble keeping the kdtreenode at its original 8 byte size. Because we were creating nodes on the fly, we could not use the convention of storing the first child immediately after the parent in order to save holding the first child pointer. I got around that by storing child pointers next to eachother. Although we lose a little of the caching benefits of having one child immediately next to the parent, this is a worthwhile optimization simpley because it keeps the node at 8 bytes. The other problem was where to stick the bit that denotes a lazy node. Since both the lazy node and leaf node holds a pointer in its bottom 4 bytes, we could hide the flag in the lowest order bit and just mask it out when using the pointers. Hence, the kdtree node is still 8 bytes.
The other major optimization was to do lazy refining of primitives. My approach was pretty simple. No refining of the primitive initially. When we hit a leaf, and the primitive(s) is unrefined, we refine it one level then create a new kd-tree with the refine primitves, then intersect the tree. (The object-oriented approach is nice, cuz we can call Intersect regardless of whether its a real primitive or another kdtree).
Final Images Rendered with my implementation of heightfield.cpp
killeroos-view1.pbrt (Killeroos visible)
Upload new attachment "killeroos-view1.jpg"
|
KD Tree |
Lazy KD Tree |
Ratio |
build time (secs) |
22.2 |
0.0 |
0% |
total time (secs) |
35.3 |
32.6 |
92.4% |
Num of nodes made |
2.754M |
1.359 M |
49.4% |
Triangle ray intersections |
673.2k |
826.8k |
122.8% |
"killeroos-view2.pbrt (Killeroos invisible)"
Upload new attachment "killeroos-view2.jpg"
|
KD Tree |
Lazy KD Tree |
Ratio |
build time (secs) |
21.6 |
0.0 |
0% |
total time (secs) |
32.4 |
11.4 |
35% |
Num of nodes made |
2.754M |
47 |
.0017% |
Triangle ray intersections |
758.4k |
776.1k |
102.3% |
"killeroos-view3.pbrt (close-up)"
Upload new attachment "killeroos-view3.jpg"
|
KD Tree |
Lazy KD Tree |
Ratio |
build time (secs) |
21.8 |
0.0 |
0% |
total time (secs) |
36.5 |
23.0 |
87.7% |
Num of nodes made |
2.754M |
373.2k |
13.6% |
Triangle ray intersections |
644.2k |
1.018M |
.633% |
"plants-view1.pbrt"
Upload new attachment "plants-view1.jpg"
|
KD Tree |
Lazy KD Tree |
Ratio |
build time (secs) |
38.7 |
0.0 |
0% |
total time (secs) |
420.6 |
910.4 |
216.4% |
Num of nodes made |
14.838M |
4.044M |
27.25% |
Triangle ray intersections |
25.887M |
27.614M |
106.7% |
"plants-view2.pbrt"
Upload new attachment "plants-view2.jpg"
|
KD Tree |
Lazy KD Tree |
Ratio |
build time (secs) |
37.4 |
0.0 |
0% |
total time (secs) |
928.9 |
1585.1 |
170.6% |
Num of nodes made |
14.838M |
5.166M |
34.8% |
Triangle ray intersections |
25.887M |
31.224M |
120.6% |