The BeRays Ray Tracer

Regan presents BeRays, an object-oriented ray-tracer application that makes it easy to plug in new rendering ideas.


November 01, 1999
URL:http://drdobbs.com/tools/the-berays-ray-tracer/184411102

Nov99: The BeRays Ray Tracer

Regan is a programmer in Sydney, Australia. He can be contacted at rrussell@ c2.telstra-mm.net.au.


BeOS, an operating system from Be Inc. (http://www.be.com/), was designed for digital media applications on PCs and Internet appliances. Consequently, BeOS supports a 64-bit journaling filesystem, real-time multimedia streaming, pervasive multithreading, and multiprocessor support.

There are two types of Be applications -- GUI applications and console applications. A console application can be written in C and is comparable to UNIX programming. In fact, many tools are GNU based. The default shell is the BeShell, a slightly modified Bourne Again Shell (BASH), popular with users of FreeBSD, Linux, Solaris, and the like. The open-source versions of Lex and YACC -- Flex and Bison -- are also available. Additionally, for the Intel version, NASM (assembler with MMX support) is available.

The Be GUI API, on the other hand, is C++ based. Unlike Windows (and XII), which lets you write apps in C using the raw Win32 API without using C++ and MFC (or Motif applications without a C++ wrapper), you cannot write GUI-based BeOS applications in C. To do anything interesting, applications must create objects derived from the classes BApplication, BWindow, and BView in C++.

In this article, I present a GUI-based Be ray-tracer application called "BeRays" -- an object-oriented application (written in C++) that makes it easy to plug in new rendering ideas. Most public source ray tracers are written in C and simulate inheritance in case statements or by implementing their own virtual function tables (commonly known as "vtbls") by manipulating function pointers. In BeRays, I use flex and bison to generate the parser for the input language.The source code and related files for the BeOS implementation of BeRays is available electronically; see "Resource Center," page 5. Although I've tested BeRays on both Intel BeOS 4.5 and PowerPC BeOS 4.0, I'm only providing the Intel version with Intel project file. To build a PowerPC version, you simply create a new project and add all the required source files and libraries.

BeOS Kits

The keys to developing BeOS applications are kits (classes and interfaces to classes that provide support) and the Smalltalk Model View Controller (MVC) concept. There are a number of kits, including:

Kits talk to servers. Servers are daemon processes that manage things. One such server is the application server, which includes a psycho-killer thread that goes around cleaning up processes that have gone wild, such as UNIX zombie processes. Kits also require a library and possibly a header file to be included. For example, the OpenGL kit needs libGL.so and be/opengl/GLView.h.

Kits are not only threadsafe but spawn threads themselves. Creating a window (from the Interface kit and managed by the application server) creates a separate thread to manage it. A group of threads are a team and an application is usually a team of a few threads.

Many objects in kits take responsibility for their subobjects. For example, a BMenu will delete each BMenuItem in its destructor. It is often valid to allocate variables without keeping track of them and not expect memory leaks. BeRays contains a few new statements without matching deletes. The general case still remains that if the OS allocates it, the OS frees it. If the application allocates it, then the application should free it if the OS will not. BList, for example, will not delete attached objects, but will delete its list nodes.

Messages are generated from events. Events can have their handler specified at creation time. For example, the BFilePanel in BeRay's ray tracer directs its messages to the parent window. The messages the parent receives depend on the user choices when the dialog is shown. Some events are handled by member functions for example BWindow::FrameMoved(). Threads do not receive messages. Message handlers receive messages.

The BeRays Ray-Tracer Application

The major components of the BeRays ray tracer I present here are the user interface, rendering engine, and rendering-engine support. The main function fires off the user interface components, and the rendering engine is then constructed and rendered (via the Go menu item, handled by the MessageReceived member function).

The application starts in main and creates a new RaysApp application, Runs the application, and then deletes the application when the Run returns. The RaysApp's (subclassed from BeApplication) constructor creates a RaysWnd (subclassed from BWindow). The window handles messages and the constructor creates a view. The RaysView constructor creates BMenuItems and a BMenu and attaches the menu items to the menu and the menu to the view. The message is directed to the window message handler when the menu item is constructed.

The Go member is called from the window's message-handling function. The RaysWnd MessageReceived member is called when there is a MOPTION_RENDER message generated from the menu item. The menu item is created in the window's constructor with BMenuItem('Render', new BMessage(MOPTION_RENDER ) );.

Another message the windows message handler deals with is the file open menu message. The file is read and input is passed to the flex scanner and bison grammar parser. The parser calls the MultiRender member to create new objects. Listing Two is the BNF grammar for the input language.

The general approach I take to ray tracing is not new and is well documented by Glasner, Foley and van Dam, Heckbert, Watt, Watt and Watt, and others, including DDJ (see, for instance, "RAY: A Ray-Tracing Program in C++," by Alain Mangen, July 1994; and "Ray Tracing," Daniel Lyke, September 1990). It is fairly well understood that backward ray tracing starts at an eye or camera position and rays are traced through a view plane toward a scene full of objects. If the ray intersects an object, other rays are traced from the surface of that object for shadows, shading, and reflection. If the ray does not intersect with an object, a background color is set. Forward ray tracing starts at a light source. Backward tracing is also known as "persistence of vision."

Because a photon is its own antiparticle, a ray going forward in time from a light source to a light receptor can be considered the same as its antiparticle (itself) going backwards in time from the receiver to the transmitter. This has the added bonus that rays emitted from the source but never reach the receiver are not considered. This is a significant speed improvement. For a photon travelling at the speed of light, time has no meaning.

For all rows and columns of the image, the Go member function constructs a ray starting from the viewpoint and starts a (potentially recursive) Trace through the scene, determining intersections of the ray with scene objects. The result of the Trace is passed to the Shade routine, which determines a color (possibly by calling Trace) and passes it back up to Go where it is set. The RenderEngine also maintains a list of scene objects and a list of light sources.

The Trace iterates through a list (BList) of scene objects and determines the rays intersection (or lack of) by using the scene objects intersection function. The Shade member uses the scene objects CalcNormal interface to determine the amount of shading. In fact the Trace and Shade members directly or indirectly use a lot of support functions.

SceneObject is a base class for any objects that appear in a scene. SceneObject is used in Trace, Shade, and LightSource's GetColour member for shadow determination. Scene objects need to support three interfaces: Construction, Intersection, and Calculate Normal. Light source, sphere, and plane are types of scene objects. New objects and variations of existing objects can be added at will. Scene objects store properties as a surface object.

Light source is a type of scene object and is used in the shade routine. It contains the light's color and position and has interfaces for getting the color and ray intersection. The method for getting the color determines if there are objects between the light source and the object the traced ray has stuck. This is used in shadow determination and is called a "shadow feeler."

In addition to scene objects, the MultiRender class uses a color class to represent colors such as the light to define a scene. The color class stores red, green, and blue triples. It is used in shading and specifying objects properties. Colors can be specified in the range of 0 to 1.0 or in 0 to 255. They are automatically converted.

Vectors are used extensively through the render engine to generate an image from the definition. The vector class is a mathematical entity for storing x, y, and z triples with operator overloading for vector operations. The methods and operations include length, rotate, reverse rotate, min, max, ~ (normalization), ^ (cross product), % (dot product), * / +, and -. A ray is two vectors from an origin to a direction. All vectors only contain a direction as they start from a common origin of (0,0,0).

The generation of the image starts with the viewpoint. The viewpoint is an eye position or camera position. A viewpoint contains four vectors: up, right, location, and direction. Rays originate from the viewpoint and once constructed with MakeRay, the viewpoint is discarded.

With future development in mind, I defined an image class with a red, green, and blue channel bitmap so that per channel image processing could be done later.

To build BeRays, you start at the command line and compile the lex (flex) and yacc (bison) specifications into C sources with the make command and a standard Makefile. The project can then be compiled and linked with the Build menu item from the IDE.

When running BeRays, you are presented with a window, blank white view, and menu bar. The menus include File, Go, and Help. The file menu consists of Open scene file and Quit. The Go menu contains the item Render and the Help menu contains About. The scene file is created with a text editor like vi. The program on start up does not contain a scene, so a default scene is used.

Conclusion

BeRays is simple and can be extended in a multitude of ways. Different illumination methods could be tried, as well as adding bounding volumes, BSP trees, and octrees. One major problem to solve is spatial aliasing or jaggies. Antialiasing techniques (such as stochastic ray tracing) could also be applied. Image processing could be integrated. Other types of objects can be added: You need to create a derived class for the new object and override the Intersection and CalcNormal members and add lex and yacc descriptions for the new object and a function in the render engine to add the object passed in from the parser to the scene list.

References

Glasner, Andrew S. (editor). An Introduction to Ray Tracing, Academic Press, 1989.

The Be Development Team. Be Developer Guide, O'Reilly & Associates, 1997.

Heckbert Paul S. (editor). "A Minimal Ray Tracer" Graphics Gems IV, Academic Press, 1994.

Foley, James D. Andries van Dam et al. Computer Graphics: Principle and Practice, Second Edition, Addison-Wesley, 1990.

Watt, A. 3D Computer Graphics, Second Edition, Addison-Wesley, 1993.

Watt, A and M.Watt. Advanced Animation and Rendering Techniques: Theory and Practice, Addison-Wesley, 1992.

DDJ

Listing One

#include <Application.h>
main()
{
   const int left = 10, right = 500, top = 10, bottom = 200;
   // Create application, window and view.. 
   BApplication *TheApp = new BApplication( "application/x-vnd.ddj-sample" );
   BWindow *TheWindow = new BWindow(BRect( left, top, right, bottom ),  
              "Rays", B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_RESIZABLE );
   BView *TheView = new BView( BRect( 100, 100, 200, 200 ), 
              "Rays", B_FOLLOW_ALL_SIDES,  B_WILL_DRAW );    
   // Attach view to window, show the window and run the application.
   TheWindow->AddChild( TheView );
   TheWindow->Show();
   TheApp->Run();
   // Clean up.
   delete TheView;
   delete TheWindow;
   delete TheApp;
}

Back to Article

Listing Two

DIGIT ::= 0|1|2|3|4|5|6|7|8|9
NUMBER::= DIGIT {DIGIT}
SCENE::= OBJECT | SCENE OBJECT
OBJ::= SPHERE_OBJECT | PLANE_OBJECT | BACKGROUND_OBJECT 
BACKGROUND_OBJECT::=   BACKGROUND  NUMBER NUMBER NUMBER
SPHERE_OBJECT::=    SPHERE OPEN_BRACKET_ID  NUMBER NUMBER NUMBER RADIUS 
COLOUR_REF CLOSE_BRACKET
COLOUR_REF::=   { COLOUR_VAL }
COLOUR_VAL::=   COLOUR_ID OPEN_BRACKET_ID NUMBER_ID NUMBER_ID 
NUMBER_ID CLOSE_BRACKET_ID  
RADIUS::=   { NUMBER }  
PLANE_OBJ::=    PLANE OPEN_BRACKET NUMBER NUMBER NUMBER NUMBER 
CLOSE_BRACKET
 

Back to Article


Copyright © 1999, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.