CS 348B - Computer Graphics: Image Synthesis Techniques
Homework 2 - Height Field Intersection
Assigned Thursday, April 8. Due Tuesday, April 20.
For this assignment, you will improve pbrt's support for rendering terrain - landscapes and seascapes.
To see examples of what is possible with higher resolution terrains, take a look at the entry on rendering ocean scenes from
Rendering Competition in 2001.
kind of terrain is often represented as an elevation map, or what we will call a
height field: a table indicating the height of a surface above each point of a uniform 2D grid. In other words, we are tabulating the values of a height function z(x,y) in a 2D array.
default pbrt implementation (see /pbrtsrc/shapes/heightfield.cpp) converts the
height field to a triangle mesh.
Height fields have a very orderly structure, which is ignored in simply turning them into triangle meshes. Your assignment is to apply techniques like those used for acceleration structures to develop a fast intersection routine for
height fields. An efficient solution will be substantially faster than the default implementation.
You must have pbrt installed correctly, as in homework 1.
Copy /usr/class/cs348b/files/hw2 to your working directory.
Run 'pbrt hw2.lrt sea1.lrt land1.lrt'. When you list multiple scene
files like this, pbrt simply concatenates them as input. This command
generates a fill hw2.exr, which you can convert to a tiff (exrtotiff hw2.exr
hw2.tiff) and a jpg (convert -quality 95 hw2.tiff hw2.jpg) as in homework
The height fields used for the land and water are fairly low in resolution --
just 64x64. Furthermore, the surface facets are accentuated by the fact
that the normal vectors of the surface are not interpolated for shading.
2 (Main Assignment)
You will write a fast height field intersection
routine, which will allow you to render much finer terrains, and more
You should do this by enhancing the core pbrt file, pbrtsrc/shapes/heightfield.cpp.
- Add and implement Intersect and IntersectP functions, overriding the base
class versions in shape.h
- Change CanIntersect to return true, so that pbrt uses your function
instead of the Refine method.
You should put some thought into how to do this efficiently. The lectures on acceleration structures like grids and kd-trees should suggest good approaches. Note that the
height field data is already basically in the form of a 2D uniform grid - this should be helpful for whatever method you decide to implement.
Of course, your method should be robust - be sure it can work from a variety of viewpoints (and for a variety of different
Once you've got the base intersection routines working, add the following
- Have your heightfield intersection return sensible values for the u and v coordinates, so that you can texture map heightfields in a natural way.
You can test this by finding an appropriate texture and applying it to the
land, for example.
- Perform Phong interpolation of the normals across each heightfield
triangle. It can be tricky to implement this interpolation in a way that
doesn't produce visual artifacts.
There is a 1000x1000 sea terrain in /usr/class/cs348b/files/terrain/sea1000.lrt.
This might take a while to render with the default implementation!
There are also a number of other sky textures to choose from in the
Create a web page with the following pieces of information and email it to
firstname.lastname@example.org. Please be as clear and as concise
as possible, as there are 30+ students and only one TA!
- Include a paragraph describing how you implemented Steps 2 and 3.
Highlight anything you did that was unorthodox, particularly important, or
that you enjoyed discovering.
- Include an html link to your code.
- Include and label the following pictures:
- Renderings from these viewpoints (hw2-view1.lrt, hw2-view2.lrt,
hw2-view3.lrt, hw2-view4.lrt). These have been added to the
/usr/class/cs348b/files/hw2 directory. Note that, unlike hw2.lrt,
these scenes already contain the land and water, so you should render by
typing 'pbrt hw2-view1.lrt', not 'pbrt hw2-view1.lrt sea1.lrt
land1.lrt'. As an example, here is a view
- Below each picture, please report
- The rendering time with your new intersection routines (in
- The rendering time with the original default
- The percentage of the original time that your implementation takes (if this
is 200% then your code runs half the speed of pbrt's optimized K-d
The FAQ below includes a command that can be used to time your code
Note 1: Report timing results with pbrt compiled with
optimizations. You can do this by typing 'make clean', then 'make
Note 2: We of course encourage you to tune your code, but there
is no required performance to complete the assignment perfectly. If your
implementation is as fast as the optimized default pbrt implementation
(great job!), please add a comment or two to explain how you optimized
your code. You'll get the extra credit *** (see Grading
Note 3: You are free to time your code on whatever system is easiest
for you. However, please tell us about your system (OS, speed,
amount of RAM).
- Also include:
- An image showing textured land that demonstrates your (u,v)
parameterization in Step 3. You may like to omit the water to show
more of the land.
- A pair of images (rendered with your intersection routines)
with/without Phong interpolation of normals.
- An image with sea1000.lrt as a replacement for sea1.lrt (You can
render this with 'pbrt please use the first viewpoint).
sea1000.lrt is available in /usr/class/cs348b/files/terrain/sea1000.lrt.
- Q: I don't understand what this assignment is about. Doesn't pbrt
already store the height field in an acceleration structure?
A: The default pbrt implementation does store the tessellated height field
in a 3D K-d tree. This default implementation is actually highly
optimized. However, you can provide a useful specialized
implementation for height fields by exploiting the structure of the height
field. For instance, a common approach is to traverse a 2D grid,
building the height field triangles in that cell when you need to.
This can save a lot of memory for larger height fields. Another
approach would be to use a 2D K-d tree. These specializations are possible
because of the structure of the height field, and are simpler and often more
efficient than more general 3D versions. Choose an acceleration
structure that interests you and explore it in this simpler 2D space.
- Q: How do I compile pbrt with optimizations?
A: Type 'make clean' to delete existing object files and executables.
Type 'make OPT=-O2' (there's a dash before the capital O). That builds
the system with optimization.
- Q: How do I time my code for the timing results on my web page?
A: On Linux, you can use commands such as: 'time pbrt hw2-view1.lrt'. After rendering is complete, the time command will print
out a line, where the first token is the total time executed in user mode,
- Q: I can't get my intersection routine faster than the default
A: It may be challenging to get your intersection routine as fast as
the default version in pbrt, because pbrt has been quite carefully optimized
with a 3D K-d tree. It is not a requirement for the assignment
that your code run as fast as the default implementation (your routine is still
practically useful because it probably uses less memory than the
default). Nevertheless, it is possible to get your specialized height
field intersection as fast or faster than pbrt, and you can get extra credit
if you succeed in doing so.
- Q: I can't seem to eliminate some black reflected regions in the
foreground of the image when rendering with sea1000.lrt. Is that okay?
A: You may get some black regions where the water is reflecting far away
from the forward direction. The sky background doesn't actually cover
the entire hemisphere, and the water will appear black where the correct
reflection direction misses the sky geometry (a cylinder). This
doesn't happen in sea1.lrt, but it does in the foreground of sea1000.lrt
where the water is more bumpy. However, if you are getting black
speckles on the crests of your waves in the distance, then this is likely an
artifact of your Phong interpolation scheme; this is something you should
- Q: There doesn't seem to be a heightfield project in the Windows build
A: That's correct. However, Eilene Hao has kindly created and
shared a heightfield VS project that you can add to the pbrt VS
solution. It's available at /usr/class/cs348b/files/heightfield.vcproj.
- Q: Exactly what time on April 20 is the assignment due? It doesn't
A: Please send us the write-up for your URL by the end of the day (11:59
- Q: I'm having trouble setting a gdb breakpoint in my heightfield plugin
code. What's the best way to do this?
A: The main difficulty is that plugin code is loaded dynamically by pbrt.
Greg recommends: (1) Load up the executable and put a breakpoint in
MakeShape(). Then step OVER the code that loads the DLL. Then you can put
breakpoints in your code. (2) Use printf.
Matt adds: (1) Best thing to do is set a breakpoint in
Scene::Render, then step into the accelerator and then into your
intersection code. At that point, you should be able to set other
breakpoints in your module. (2) If you want to debug your constructor, set a
breakpoint in pbrtShape(), which eventually calls your constructor.)
This homework will be graded according to the
0 - Little or no work was done
* - Significant effort was put into the assignment, but not everything
** - The algorithm you implemented was functionally complete;
it passed all of our tests.
*** - (Extra credit) You demonstrate that your intersection routines are
as fast as the original (optimized) pbrt implementation.
You will get one grace day for this assignment, which you may save and use on
homework 2 or 3 (but not on the final project).
Copyright © 2004 Pat Hanrahan