This page describes the Release 3.0 of the RayTrace software, which is available freely from http://math.ucsd.edu/~sbuss/MathCG. The software is open source and may be used freely for any purpose, as long as all uses are acknowledged by emailing the author and by including acknowledgements with all distribution of the software and all articles or images created with the software or its derivatives.
The software is solely written by Sam Buss, Department of Mathematics, University of California, San Diego, firstname.lastname@example.org. The kd-tree code is a re-write of an earlier software project by Alex Kulungowski under the supervision of Sam Buss.
Comments, suggestions, and bug reports should be sent to Sam Buss. The software has no warranty, is distributed "as is", and is supported only haphazardly.
The original version of the RayTrace code was released as an adjunct to the book 3D Computer Graphics: A Mathematical Introduction with OpenGL, by Sam Buss, Cambridge University Press, 2003. The RayTrace code consists of large set of low-level routines that form the core of a ray tracing software package. These routines include intersection testing of rays against objects, lighting calculations, reflections, etc. Features include:
The above features are robustly and efficiently implemented in C++. In addition, high level programs are provided that use these low-level routines to display ray-traced scenes. However, it is expected that users will rewrite the high-level routines for their own purposes. The intent is that users of the code will provide extra programming for features such as distributed ray tracing, path tracing, photon mapping, radiosity, etc. In other words, the provided ray tracing software is not a complete finished product, but rather is a set of low-level tools.
The first major upgrade was (appropriately enough called release 2.0). This included some bug fixes, plus some new features. The new features include:
Release 3.0 includes some major enhancements to the software. These include:
Suggestions for enhancements in future releases are welcome, although there are no promises of what can be done. One big area for further improvement is supporting more test file formats, including a developing yet another text file format that will support all the features of the RayTrace code.
The base documentation for Release 1.0 is contained in the appendix to the book mentioned above. That appendix is still the primary source of documentation for the ray trace software. However, the present document provides documentation for the new features in Releases 2.0 and 3.0. In addition, users are strongly encouraged to read the source code to understand how the software works.
At some day in the future, I hope to get around to writing better documentation. (Please feel free to volunteer to write some for me...)
Compiling the source code. The downloadable .zip file includes all source code, please project files for use with Microsoft's Visual Studio C++.NET 2003. If you are using Visual Studio C++.NET 2003 or later, you should be able to compile and run the program by opening the solution file RayTraceKd.sln in the directory RayTraceKd. The "solution" consists of six distinct "projects": RayTraceKd is by default the main project containing the main program. RayTrace also contains a main program: by making RayTrace the currect project, you can also compile and run RayTrace.
Warning: A common error is setting a project other than RayTraceKd or RayTrace the main project. This will give you an error message if you try to compile and execute the code as it will not know where the executable file is.
The difference between RayTraceKd and RayTrace is that RayTraceKd uses the new features of Release 3.0 (scene descriptions, input from files, and acceleration with kd-trees.) RayTrace, however, uses the more primitive interface of the earlier Releases.
If you do not use a recent version of Visual Studio or if you have problems using the solution file, you will need to build your own project structure or makefiles. There are seven directories of source files: RayTraceKd, RayTrace, DataStructs, Graphics, OpenglRender, RayTraceMgr, and VrMath. It is recommended that you keep this directory stucture intact. The last five, DataStructs, Graphics, OpenglRender, RayTraceMgr, and VrMath, generate object file libraries (static libraries). The first two, RayTraceKd and RayTrace generate executable problems (Win32 console applications). The main programs are found in RayTraceKd.cpp and RayTrace.cpp.
Be sure to compile with optimization turned on ("Release" mode in Visual Studio C++) in order to get better execution speed!
Project dependencies are:
Execution: When the ray tracing program is run, it first shows a winder with the scene rendered very crudely with OpenGL. If you press the space bar or type "g", the same scene will be re-rendered using ray tracing. By using the arrow keys (to control view direction) and the Home/End keys (to control distance), you can adjust the view point while viewing the scene with OpenGL. You may also resize the view window, and this also reverts to OpenGL rendering. After you have the view adjusted well and the window sized correctly, press "g" or the space bar again to render the scene in OpenGL.
(The purpose of the OpenGL rendering is to compensate for the slow speed of ray tracing. As ray tracing becomes faster, this becomes less important. Indeed for complex scene, rendered in small windows, the ray tracing renderning is even faster than OpenGL-based rendering.)
In RayTraceKd.cpp, there are three possible settings for a #define'd variable MODE. Setting MODE equal to 1 renders the test scene from the old RayTrace2 progam. MODE equal to 2 reads a .obb file to load the scene. MODE equal to 3, reads the scene from a .nff file (neutral file format) file. You should try re-comnpiling with all three settings to the MODE variable and see how they work. You may change the filenames in the source code of RayTraceKd.cpp to read in other files. (Several sample files are contained in the directory RayTraceKd.
A SceneDescription object includes a complete description of the viewable objects in a scene, the materials and textures used, the camera position and view, and the lights. A good example of how to set up a scene is to look at how the program RayTraceSetup2.cpp in the RayTraceKD project. The basic method is to construct the various elements in the scene in the usual way and just add them to the SceneDescription. For the descriptions below, let TheScene be a scene description object.
Memory management: Call TheScene.DeleteAll() to free all objects stored in the scene. This is a convenient way to avoid memory leaks. For finer control of memory, there are individual methods DeleteAllViewables(), DeleteAllMaterials(), DeleteAllTextures(), DeleteAllLights() to selectively deallocate different types of objects.
Advanced usages: You may let the SceneDescription fully handle allocation and deallocation of objects if you wish. For example, call TheScene.NewMaterial() to get a pointer to a new Phong Material object that is in the scene, then set the properties of the Material. TheScene.NewMaterialCookTorrance() can be used in the same way to get a Cook-Torrance material object in the scene. There are lots of similar methods for allocating different kinds of texture map objects. (So far, I have been too lazy to implement the same kind of routines for ViewableObjects, but if someone requests this, I will probably be willing to do it.)
Files of type .nff (neutral file format) and .obj type are two standard methods of specifying scene geometry. The release 3.0 version of the RayTrace software now supports reading scene descriptions from these files. The RayTraceMgr subproject (new to release 3.0) contains functions LoadNffFile and LoadObjFile. The function templates are:
bool LoadNffFile( const char* filename, SceneDescription& theScene );
bool LoadObjFile( const char* filename, SceneDescription& theScene );
These function calls read the .nff or .obj file named filename (usually the filename ends with .nff or .obj) and loads all the relevant data into the Scene Description theScene. If there objects already in the Scene Description, they are left untouched.
Not all features of the .nff and .obj files are supported. For instance, triangles with normals at vertices ignore the explicitly specified normals and just use the true normal to the triangle. (This is because triangles with normals specified at vertices are not currently supported by the RayTrace software.) For .nff files, the following items are supported: polygonal patches, cylinders, cones, spheres, colors (including reflection and transmission), and all the view point related commands. For .obj files, the following items are supported: vertices and faces.
The .obj file format does not specify viewer position, so you must add this to the Scene Description in your own code. Neither format supports specifications of lights, so again, these must be added explicitly by your software to the Scene Description.
For examples of how to use .obj and .nff file loading, see the program RayTraceKd.cpp.
The method of using the kd-tree acceleration structures is illustrated in RayTraceKd.cpp. The kd-tree code is written with a very general interface so it can be used by a variety of applications. If you are just using the RayTrace software for ray tracing, you can just use the code in RayTraceKd.cpp. For a more detailed explanation of how the kd-tree code can be used for other applications, read on....
1. For kd-trees, the objects in the tree are referred to with long integer values, usually called objectNum's. To create a kd-tree, call:
KdTree( long numObjects, ExtentFunction* extentFunc, ExtentInBoxFunction* extentInBoxFunc );
(There is an alternate method of doing the same thing with the function BuildTree.) The extentFunc is a callback function that when invoked as
ExtentFunction( long objectNum, AABB& boundingBox );
must fill in the axis aligned bounding box boundingBox with the minimum and maximum extents of the object in the x,y and z directions. The function extentInBoxFunc must be a call back function, that when invoked as
ExtentInBoxFunction( long objectNum, const AABB& clippingBox, AABB& boundingBox );
finds the smallest bounded box that encloses object number objectNum intersected with the box clippingBox. Neither extent function needs to return exactly the best bounding box, however, the bounding box returned must include the (clipped) object and best performance is obtained if the minimum size bounding box is returned.
2. To traverse ray through a kd-tree call
Traverse( VectorR3& startPos, const VectorR3& dir,
PotentialObjectCallback* pocFunc, double seekDistance = 0.0, bool useSeekDistance = false );
The values startPos and dir specify a ray. Whenever an object is *potentially* intersected by the ray, the callback function pocFunc is invoked as:
bool pocFunc( long objectNum, double& retDistance );
If the ray hits the object, pocFunc sets the distance
value retDistance and returns true. Otherwise, posFunc returns false.
In the end, Traverse returns the value true provided an object is hit that is the closest object that can possibly be hit (or, within distance less than seekDistance if useSeekDistance is true). The callback function pocFunc is responsible for remembering which object was the closest hit object!!
There is an alternative method of calling Traverse that specifies a PotentialObjectListCallback function rather than a PotentialObjectCallback. The "List" form takes a list of object numbers, rather than a single object number, but otherwise acts similarly.
Use the method DumpBmp( char* filename ) in PixelArray.h/.cpp to write the current image to a file. This is a convenient way to output screenshots that are higher resolution than your monitor, or to output multiple screenshots to make a movie.
The file TransformViewable.h and TransformViewable.cpp have methods
TransformWithRigid( ViewableBase* theObject, RigidMapR3& theTransform );
which update the position and orientation of the
viewable object theObject with the rigid map (affine map)
defined by theTransform. This is useful for animation,
or can also useful if you want to place objects in a standard position and orientation
and then define their position and orientation afterwards.