Advanced Graphics
academic year 2014/15 โ 2nd period
Theoretical Assignment #1:
Raytracing and Data Structures
Author:
Published:
Due on:
Michael Wand (
)
Fri, Nov 14 2014
Wed, Nov 26 2014 (interviews)
Instructions
This assignment sheet has two assignments that you have to prepare for the interviews on Wednesday,
November 26 2014. There is an optional third problem set that is not required and has no influence on
your grading (but it is interesting to think about, to get a deeper understanding of the theoretical /
algorithmic side).
Please prepare a short write-up (one separate write-up for each student) for the two mandatory
assignments and prepare yourself to explain the solutions during the interviews. Every student will be
graded individually, based on her/his answers to the questions. The write-ups are not marked, their
purpose should be to help presenting your solution, and possibly guide your answers. Although grading
is individual, we encourage you to work in groups and discuss the solutions. Nonetheless, everyone must
be able to present the full set of solutions individually.
Assignment #1: Spatial Data Structures
(30 + 10 + 20 = 60 points)
a) Write an algorithm (suitable pseudo-code) that constructs an octree from a set of points
ฮฉ = {๐ 1 , โฆ , ๐ ๐ } โ โ3. Elementary geometric computations do not need to be elaborated (such as
intersection/overlap calculations, sorting, etc.).
Solution
struct Node { // an octree node
list<Vector3f> points;
Node* children[8];
}
Algorithm shrinkBB() // compute one of the child-bounding boxes of the octree
input BoundingBox3f bb;
input int index;
returns Node* result;
{
for (int dim=0 to 3) {
if (index and 2^dim โ 0) {
bb.lowerBound[dim] = bb.center(dim);
} else {
bb.upperBound[dim] = bb.center(dim);
}
}
return bb;
}
Algorithm buildTree()
input list<Vector3f> points;
input BoundingBox3f bb;
returns Node* result;
{
Node *result = new Node();
initialize all result.children to NULL;
if (points.count() < MAX_NUM_PTS) {
result.points = points;
return result;
} else {
list<Vector3f> sublists[8];
for all (p in points) {
int index = 0;
Vector3f boundingBoxCenter = bb.center();
if (p[0] > boundingBoxCenter[0]) index = index OR 1;
if (p[1] > boundingBoxCenter[1]) index = index OR 2;
if (p[2] > boundingBoxCenter[2]) index = index OR 4;
sublist[index].addPoint(p);
}
for (int i=0 to 7) {
if (NOT sublist[i].empty()) {
result.children[i] =
buildTree(sublist, shrinkBB(bb, index));
}
}
return result;
}
}
b) What is the worst-case complexity of your algorithm (time/memory)? Why?
Solution
The algorithm can use infinite time if the smallest spacing between points is very small in comparison to
the diameter of the point set. Realistically, this is not a problem. If we call the height of the tree h and
the number of points n, we obtain a worst-case run-time bound of O(nโ
h) because at every level of the
tree we will have to go through (in the worst case) all points. This case can be constructed by placing
points in spacings of 2โ๐ , ๐ = 1, โฆ , ๐.
c) Write down an algorithm that performs a ray query (ray-triangle intersection) with early ray
termination using an octree of triangles (again, no need to elaborate elementary geometric tests
such as ray-triangle intersection, or ray-box intersection, or the similar). Assume that the octree was
built with overlapping actual bounding volumes, as discussed in the lecture. Make sure that your
algorithm does not terminate too early!
Solution
The solution assumes the same octree definition as above, just with triangles instead of points in the
nodes. I also assume that triangles are stored multiple times in child nodes when overlapping multiple
boxes.
Algorithm shootRay()
input Ray3f ray;
input Node* octreeNode;
input BoundingBox3f nodeBB;
returns intersection result;
{
Initialize intersection results to โnothing hitโ, infinite distance.
for all (triangles in octreeNode) {
test for intersection;
update closest hit in results if intersection found and closer than
the closest hit in the results;
}
// bit 0==1: x right->left, bit 1==1: y top->down, bit 2==1: z back->front
int order = 0;
if (ray.direction[0] < 0) order = order OR 1;
if (ray.direction[1] < 0) order = order OR 2;
if (ray.direction[2] < 0) order = order OR 4;
for (int box=0 to 8) {
int nextBox = box XOR order;
Node *childNode = node.children[nextBox]);
if (childNode โ NULL) {
BoundingBox3f bb = node.children[nextBox];
if (intersectRayBoundingBox(ray, bb)) {
if (no closer hit in results than bounding box hit) {
shootRay(ray, childNode, bb);
update closest hit in results;
}
}
}
}
}
Assignment #2: Raytracing
(15 + 10 + 15 = 40 points)
The goal of this assignment is to work out the ray equations for recursive raytracing (how to setup
primary and secondary rays; we leave out the shadow rays โ this is straightforwardโฆ).
a) Primary rays: We are given a camera position ๐ฑ๐๐๐ (projection center), and an orthonormal camera
coordinate system (๐ฏ, ๐ฎ, ๐ซ) (where ๐ฏ is the view direction, ๐ฎ is the up direction, and ๐ซ = ๐ฏ × ๐ฎ). We
also know that we have a screen of ๐ค × โ square pixels, and the vertical viewing angle of the full
viewport is ๐ผ. For these parameters, compute the ray (starting point, direction vector) for pixel
(๐ฅ, ๐ฆ) โ {1 โฆ ๐ค} × {1 โฆ โ}. (This means, provide a formula or a sequence of computations that
determine the ray). Hint: Sketch the setup first in a schematic drawing.
Solution
Here is my source code:
float tanOfHalfViewingAngle = tan(vf.getVerticalFieldOfView()/180.0f*M_PI/2.0f);
Vector3f up = cam.getOrthoNormUpDirection() * tanOfHalfViewingAngle;
Vector3f right = cam.getOrthoNormRightDirection() * tanOfHalfViewingAngle;
Vector3f view = cam.getOrthoNormViewDirection();
Ray3f ray;
ray.origin = cam.getPosition();
float aspectRatio = (float32)width/(float32)height;
for (int y=0; y<height; y++) {
float rely = 2.0f * (height/2 - y) / (float)height;
for (int x=0; x<width; x++) {
float relx = 2.0f * (width/2 - x) / (float)width * aspectRatio;
ray.direction = view + right * relx + up * rely;
tracePrimaryRay(ray);
โฆ process resultโฆ
}
}
b) Secondary rays I: A ray with direction ๐ซ๐ โ โ3 intersects a reflective object at point ๐ฑ โ โ3 , which
has a surface normal of ๐ง๐ฑ โ โ3. Compute the outgoing reflected ray.
Solution
Outgoing ray direction: ๐ซ๐ = ๐ง๐ฅ โ
2โฉ๐ง๐ฅ , ๐ซ๐ โช โ ๐ซ๐
Outgoing ray ๐ซ(๐ก) = ๐ฑ + ๐ก โ
๐ซ๐
c) Secondary rays II: A ray with direction ๐ซ๐ โ โ3 intersects a transparent object at point ๐ฑ โ โ3 , which
has a surface normal of ๐ง๐ฑ โ โ3. The index of refraction of the medium the ray is coming from is ๐1
and for the target medium it is ๐2 . Compute the outgoing reflected ray. Hint: Do not forget to include
a special case (if-clause) for total reflection!
Solution
Here is my code:
Vector3f refract(const Vector3f &incomming, const Vector3f &surfaceNormal,
const float inOutDensityQuotient) {
float nMultI = surfaceNormal*incomming;
float det = 1-sqr(inOutDensityQuotient)*(1-sqr(nMultI));
if (det < 0.0f) {
return mirrorVector(incomming, surfaceNormal);
} else {
return surfaceNormal*(-inOutDensityQuotient*(nMultI) - sqrt(det)) +
incomming*inOutDensityQuotient;
}
}
Optional Problems: Theory
(ungraded; not required for interviews, just food for thought)
The problem with the memory bound for octrees can be overcome by not storing useless splits. A
useless split is a node that has only one child node. Instead of storing linear chains of such nodes we can
introduce shortcuts. A shortcut is an appropriately flagged child node that directly goes to a smaller
bounding box such that it actually splits at least two points into different child nodes (see Figure 1).
Figure 1: Building a quadtree with shortcuts (same principle for octrees) โ we leave out nodes that do not separate any nodes
(a) Describe how you have to augment your algorithm such that it builds an octree with shortcuts. The
data structure should have ๐ช(๐) memory requirement and should be guaranteed to be built in finite
time for any input. (Assume that this might comprise points outside the observable universe and
distances below the Planck length.)
Solution
The idea is to modify the algorithm above: Before creating child nodes (else part of the outer if),
compute the bounding box of the remaining points and shrink them to an octree node that will split the
points.
This shrinking can be implemented as follows:
โ We determine the right level by taking the maximum bounding box side length and computing the
base-2 logarithm (this gives the depth).
โ Then, the x,y,z coordinates are quantized to the regular grid at that level.
โ Rounding up & down to the grid values for all coordinates of all points gives the bounding box.
(b) Try to build an octree with shortcuts in guaranteed ๐ช(๐ log ๐) time. This is more difficult than the
worst-case quadratic algorithm that is obtained from a straightforward recursive splitting.
Remark: The issues discussed in this optional problem set are theoretically important but, in my
experience, have no practical relevance.
Solution
The problem is that one child node might contain most of the triangles while the others contain only a
constant number of triangles. To avoid this situation, we continue to divide child lists into child nodes
until each list contains less than ๐/2 triangles. Then we apply the algorithm recursively to all child lists
that still contain more than the maximum number of triangles per node (see Figure below).
Figure: Asymptotically optimal octree construction in ๐(๐ log ๐) time and linear space.
If we can perform this divide step in linear time, we will obtain ๐(๐ ๐๐๐ ๐) construction time: The length
of the remaining lists will be less than half the size of the input so that ๐(๐๐๐ ๐) steps with ๐(๐)
running time each are necessary. How can we perform a linear time division step? To divide a point set
(i.e. the centers of the triangles) into small sublists according to octree boxes, we first interpret an
octree node as a binary tree of splits in the three coordinate directions (1 in x-, 2 in y-, and 4 in zdirection). In order to perform one such binary split efficiently, we start with three sorted doubly-linked
lists, containing the triangles sorted by the x-, y-, and z-coordinates of their centers. For the root node,
the lists are sorted explicitly in ๐(๐ ๐๐๐ ๐) time. For the recursive calls to the divide algorithm, they will
be constructed on the fly. To perform one split in one of the three possible directions we first copy all
three lists. Then, we go through the corresponding list in alternating front and back order. Thus, we can
find the first entry that exceeds the splitting plane in the middle in time proportional to the size of the
smaller of the two sets. The triangles corresponding to the smaller of the two sets are deleted from the
lists. In the copy of the lists, the triangles are not deleted but a pointer to the corresponding child node
is stored at the list entry. The remaining lists are still sorted and contain all triangles for the larger of the
two halves. Thus, we can apply the technique recursively until the remaining child list contains less than
half the triangles of the original list. Afterwards, we must sort the constructed child lists. We do this by
going through the three copies of the original triangle lists and follow the pointers to the child nodes in
order to append the triangle to the corresponding child list. Then, we have the same situation at each
unfinished child node as at the beginning and we can call the construction procedure recursively for
each unfinished child.
Care must be taken to integrate the short-cut technique into this scheme. To do a short-cut, we must
augment the splitting planes: Instead of using the middle of the current node, we have to calculate a
potentially shrunk child node and use the middle of this node for splitting. The corresponding
calculations can be done easily every time we start a new node: As we have sorted lists for the triangles
in this node, we can determine the bounding box of the triangles in all three dimensions and do the
short-cut calculations as described above.
Using this technique, we can construct an octree with short-cuts in optimal ๐(๐ log ๐) time using
optimal ๐(๐) space. The construction time is optimal because the data structure can be used to output
a sorted list of triangle centers in one of the three coordinate directions. Sorting of a set of arbitrary real
numbers is an โฆ(๐ log ๐) problem. Hence, the construction time cannot be improved asymptotically.
The optimality of the space requirements is obvious.
Remark: The solution above was proposed in the following paper (in the context of a slightly different
spatial hierarchy).
Callahan, P.B.: Dealing with Higher Dimensions: The Well-Separated Pair Decomposition and Its
Applications. Ph.D. thesis, John Hopkins University, 1995.
© Copyright 2026 Paperzz