CS 348B - Computer Graphics: Image Synthesis Techniques
Homework 2 - Height Field Intersection
Assigned Thursday, April 7. Due Tuesday, April 19.
Description
For this assignment, you will improve pbrt's support for rendering
terrain in scenes like landscapes and seascapes. Terrain is often represented as
an elevation map, or what we will call a heightfield: a 2D table
indicating the height of a surface above each point of a uniform grid. In other
words, a heightfield is tabulated form of a height function z(x,y) stored in a
2D array.
pbrt has no native support for rendering directly from heightfield
data. Instead, it converts heightfields into triangle meshes by connecting
adjacent height values in the array. This approach is general but relatively
inefficient; by converting to triangles we lose the orderly structure of the
heightfield, which can be exploited to render more efficiently. Your assignment
is to apply techniques similar to those used for grid-based acceleration
structures to develop a fast intersection routine for heightfields.
Step 1
Download this zip file containing several pbrt
scene files, heightfield data, and textures. The UNIX version of pbrt
should build the heightfield shape plugin by default. If you're using windows,
copy the heightfield.vcproj file to pbrt/win32/Projects/ and add the
project file in the pbrt solution. After you have built the heightfield plugin,
run pbrt on scenes: hftest.pbrt and landsea-0.pbrt,
and you should see output images (hftest.exr and landsea-0.exr) that look like
this:
The 4x4 heightfield in hftest.pbrt (left image) is designed for
debugging; use this file as you develop and test your intersection algorithm.
The landsea-0.pbrt contains two 64x64 heightfields (land and sea) and
there are several views of this scene (0-3). landsea-big.pbrt contains
a 1000x1000 heightfield and will likely take a while to render and consume loads
of memory in the process.
Step 2
Your assignment is to write a fast heightfield intersection algorithm, which
will allow you to render higher resolution terrains more quickly than the
current implementation. You should do this by modifying the existing 'pbrt/shapes/heightfield.cpp'
file; you do not need to change other pbrt files. Specifically, you need
to:
- Add and implement Intersect() and IntersectP() functions, which will allow
the renderer to intersect a ray directly with the heightfield.
- Change CanIntersect to return true, so that pbrt uses your functions
instead of the Refine method to convert to a triangle mesh.
Your implementation of Intersect() and IntersectP() will likely use an
acceleration structure like a 2D uniform grid, quad-tree or 2D kd-tree.
Feel free to peruse the pbrt source for examples, but make sure you
understand any code that you borrow.
Step 3
Once you have the fast intersection routines working, add the following
additional features:
- Compute the u and v coordinates so that you can texture map heightfields.
You can test this by rendering the texture.pbrt scene, which maps
the land heightfield with a colored grid texture.
- Perform Phong interpolation of the normals across each heightfield
triangle. See the text book for information about Phong interpolation.
Here are some example images of what you should see after completing the
steps above:
Suggestions
- Make sure you understand how pbrt deals with transformations
(e.g. object-to-world)
- You should understand the GetShadingGeometry() function and how
it's used by the integrator to shade an intersection point. You will need to
implement this as a member function in the heightfield class (for Step 3).
- It will probably be difficult for your algorithm to out perform pbrt,
which uses a highly optimize 3D kd-tree to render triangle meshes. However,
you should try to make your code as fast as possible.
Submission
In the archive that you downloaded in Step 1, you will find a
"submission" directory. Copy this directory to your web space,
and edit index.html to include your renderings and a description of your
approach. The items that you have to replace are marked in
green.
- Please do NOT include your source code (you will email it to us, as
described below).
- Use exrtotiff to convert your .exr renderings to tiffs, and then convert
these to jpgs to include in your web pages.
- For the timings, compile the system with optimizations.
Send an email to cs348b-spr0405-staff@lists.stanford.edu
with the following and only the following:
- Title of exactly "cs348b HW2"
- The URL of your web page
- Your modified heightfield.cpp as an attachment
FAQ
- 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: On Unix systems, 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. On Windows, build the system under
"Release" mode.
- 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,
in seconds.
- Q: I can't get my intersection routine faster than the default
implementation!
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 landsea-big.pbrt. 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 landsea-big.pbrt
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
eliminate.
- Q: Exactly what time on April 19 is the assignment due? It doesn't
say!
A: Please send us the write-up for your URL by the end of the day (11:59
pm).
- 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.
Grading
This homework will be graded according to the following system:
0 - Little or no work was done
* - Significant effort was put into the assignment, but rendering does not
work fully
** - The intersection routine was functionally complete, but step 3 doesn't
work at all.
*** - Intersection and texture mapping worked, but normal interpolation
didn't.
**** - Intersection and normal interpolation worked, but texture mapping
didn't.
***** - All requirements satisfied
****** - All requirements satisfied, and the implementation is faster than the
original
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 © 2005 Pat Hanrahan