Software | C++ SvenGL Ray Tracing

In 2001, when studying my last year Master of Computer Science at the University of Antwerp (Belgium), I followed the course "Computer Graphics 2". We were required to do several tasks; most of them weren't really necessary, but as I am a CG freak, I did them anyway, and quickly aspired to discover all-there-is-to-know about OpenGL and its rendering techniques.

For the course, we used the book "Computer Graphics Using OpenGL", written by Francis S. Hill, Jr. The two images below show the front covers of the second and third edition, respectively left and right. This latter, third, edition features an assortiment of blue spheres on its cover; this is actually one of my creations, for which I received credit. Besides that, the book also contains numerous other colour illustrations, which are also featured in the ray tracing gallery.

    
Second edition
Prentice Hall
Amazon
     Third edition
Prentice Hall
Amazon

Below, you can find all my C++ code that encompasses a ray tracer based on an earlier version from the course book.


A Quick Overview Jump to overview menu

I learned to tame OpenGL, using anti-aliasing, Gouraud shading, depth buffering, texture mapping, animation, and various ways of rendering. You have to practice if you want to learn, so following this advice, I wrote some small demonstration programs. All these programs were based on the same rendering engine which in turn was based on GLUT (i.e., the OpenGL Utility Toolkit). The result was a flexible C++ class called TOpenGLApp that included complete lighting and rendering control, routines for automatically drawing primitives, and a complete 'flight simulator' interface which lets you fly around a custom-built 3D scene. Derived from this class are the following demo programs:

  • Cube: a rotating cube is shown with flat 2D textures painted on its sides. For the textures I used Escher's fishes, which could be replaced by photographs of the (then existing) research group 'TICO' at the University of Antwerp.
  • Landscape: whilst listening to a boring lecture on macro-economics, I worked out some form of landscape generation, together with a friend of mine. At the end of the lecture, the challenge was set and I worked hard to get a reliable interface for quickly generating landscapes. These landscapes are based on terrain maps which can be loaded from bitmap files or, if you wish, they can be generated on the fly by using fractal subdivisions. The famous Mandelbrot set really came to life with this demo; and those beautiful landscapes, based on plasma clouds, are now only a key press away. Furthermore, I introduced some sea animation using the equations for running and standing physical waves.
  • Swarm: this demo was based on my simulation of a 2D particle swarm. I took the simulation into the 3D realm which resulted in a scene in which cones are flying around you, clustering together and breaking off as they sometimes do. With minor adaptations, the demo was enhanced so you could fly in fog and see teapots circling around you (and yes, even the dreaded Borg cubes are among us).
  • SDLViewer/RayTracer: this is the final demonstration program I wrote. A ray tracer is basically a simple program that casts rays from your eye into a 3D scene and determines the intersections of these rays with all the objects in the scene. Based on this information, highly realistic renderings can be made of real-life surroundings. The only price we have to pay is a (serious) decrease in rendering speed. The program was based on the code used in the book's second edition, but with serious improvements. Foremost of all: Hill didn't do a good job at OOP in C++, so I took the liberty of rewriting all his code in a standard style (Taligent convention) that clearly reflects the logical constructions I created. The ray tracer is based on an SDL description of a 3D scene (Scene Description Language). I extended the language and gave it some powerful keywords. Time after time I improved the ray tracer and added more and more effects.

Ray Tracing Engine Features Jump to overview menu

My ray tracing rendering engine (conveniently called SvenGL) is quite powerful, here's a list of the features it supports and the effects it can handle:

  • reflection (including gloss which is blurred reflection) and refraction/transparency (including translucency which is blurred refraction), supporting interpenetrating transparent objects using a priority list. Both gloss and translucency are implemented using "non-weighted jittered discrete cone tracing" which can render more realistic reflections and refractions,
  • anti-aliasing using regular supersamples or jittered stochastic supersampling (both techniques use either a 5x5 or 9x9 grid),
  • Phong shading model for the lighting equations,
  • point lights and completely configurable spot lights,
  • hard and soft shadows using modified shadow feelers that take in account the transparency/opacity of objects,
  • multiple light sources with the capability of post-normalisation of the colors in a rendered scene,
  • light source attenuation with the distance,
  • linear atmospheric attenuation with the distance,
  • linear fog,
  • support for rendering SDL scenes (which can include other SDL scenes),
  • advanced objects such as the family of 3D algebraic surfaces quadrics (e.g., hyperbolic paraboloid), cubics, and quartics (e.g., Steiner's Roman surface),
  • various meshes (which can be Phong shaded) with support for 3VN, 3DV, 3DM files, and Wavefront's Object Files (OBJ),
  • easy to use generic world extents and world extent intersection testing for speeding up complex intersection tests,
  • CSG objects (Constructive Solid Geometry) for rapidly creating complex shapes,
  • solid 3D textures (including cubes, 1D-, 2D-, and 3D-contour lines, wood grain, marble, and cow spots),
  • procedural 2D textures (including a checker board),
  • bilinearly interpolated image 2D textures (for rendering those really realistic scenes),
  • motion blurring of shapes (based on translations, scalings, and rotations),
  • post-visualisation of the reflected/refracted rays and the normals on the surfaces,
  • completely user-configurable parameters,
  • nearly 20 MB of textures, SDL scenes, and meshes,
  • ...

Things not implemented:

  • depth-of-field,
  • adaptive tree depth control,
  • adaptive supersampling,
  • relativistic ray tracing,
  • dispersion of different wavelengths,
  • bump mapping,
  • particle rendering,
  • modeling the Klein bottle (I mean of course its 3D immersion),
  • superquadrics,
  • global illumination using photon mapping,
  • world extents for Boolean objects,
  • projection extents,
  • swept surfaces and surfaces of revolution,
  • parallel computations (ray tracing using several machines),
  • ...

Take a look at the class tree supporting the various shapes (note that many other shapes are rendered as meshes, click here to see the complete list):

    |
    +-- Material
    |
    +-- GeometricObject
        |
        +-- Boolean
        |   |
        |   +-- UnionBool
        |   +-- IntersectionBool
        |   +-- DifferenceBool
        |
        +-- Shape
            |
            +-- Cube
            +-- Mesh
            +-- Sphere
            +-- TaperedCylinder
            +-- Torus
            |
            +-- AlgebraicSurface
                |
                +-- QuadricSurface
                |   |
                |   +-- OneSheetHyperboloid
                |   +-- TwoSheetHyperboloid
                |   +-- EllipticCylinder
                |   +-- EllipticCone
                |   +-- EllipticParaboloid
                |   +-- HyperbolicParaboloid
                |
                +-- CubicSurface
                |   |
                |   +-- PinchedSurfaceWithHoles
                |   +-- PinchedSurfaceWithLobe
                |
                +-- QuarticSurface
                    |
                    +-- FourLobes
                    +-- SteinersRomanSurface
                    +-- ToothCube

Scene Description Language Jump to overview menu

SDL is an acronym that stands for Scene Description Language and it is the standard interface adopted in my ray tracing programs (SDLViewer and RayTracer). The syntax is very straightforward and this page is intended to give you a quick reference.

Note that SDL is case-insensitive and that the keywords' parameters need to reside all on one line.


Comments

A comment starts with an exclamation mark (!) and continues until the end of the line.


Lights

light     <px> <py> <pz> <red> <green> <blue>
spotlight <px> <py> <pz> <red> <green> <blue> <dx> <dy> <dz>
          <cut-off angle> <exponent>
	
Note that lights are not directional point-light sources but positional in nature. The cut-off angle for spot lights is specified in degrees.



Transformations
identityAffine
rotate         <angle> <ux> <uy> <uz>
translate      <dx> <dy> <dz>
scale          <sx> <sy> <sz>
transform      <x1> <y1> <z1> <x2> <y2> <z2> <x3> <y3> <z3>
push
pop
	
These keywords affect the current affine transformation (which resides on a stack that can be controlled with the push and pop keywords). A general affine transformation (such as a shear) can be specified using the transform keyword.



Shapes
buckyball
cone                    <distort-texture>
cube
cylinder                <distort-texture>
diamond
dodecahedron
icosahedron
mesh                    <filename (3VN, 3DV, 3DM or OBJ)>
                        <interpolate-normals>
octahedron
pyramid
sphere
taperedCylinder         <small-radius> <distort-texture>
teapot
tetrahedron
torus                   <tube-radius> <torus-radius>
oneSheetHyperboloid     <box-extent-width> <box-extent-height>
                        <box-extent-depth>
twoSheetHyperboloid     <box-extent-width> <box-extent-height>
                        <box-extent-depth>
ellipticCylinder        <box-extent-width> <box-extent-height>
                        <box-extent-depth>
ellipticCone            <box-extent-width> <box-extent-height>
                        <box-extent-depth>
ellipticParaboloid      <box-extent-width> <box-extent-height>
                        <box-extent-depth>
hyperbolicParaboloid    <box-extent-width> <box-extent-height>
                        <box-extent-depth>
pinchedSurfaceWithHoles <box-extent-width> <box-extent-height>
                        <box-extent-depth>
pinchedSurfaceWithLobe  <box-extent-width> <box-extent-height>
                        <box-extent-depth>
fourLobes               <box-extent-width> <box-extent-height>
                        <box-extent-depth>
steinersRomanSurface    <box-extent-width> <box-extent-height>
                        <box-extent-depth>
toothCube               <box-extent-width> <box-extent-height>
                        <box-extent-depth>
	
If distort-texture is 1 then textures painted on the caps of cones and (tapered) cylinders are distorted (i.e., mapped from a circle to a square); in any other case, their shapes are retained, clipping the excess material.
If you want to use a flat-shaded mesh, specify a 0 for interpolate-normals; any other value enables Phong shading of the mesh.
Note that in the case of algebraic surfaces, you must specify a bounding box (the box-extent) that bounds the surface (this bounding box is not scaled with the current affine transformation).



Boolean objects (CSG = constructive solid geometry)
difference   <left-child> <right-child>
intersection <left-child> <right-child>
union        <left-child> <right-child>
	
The children of each of these boolean objects can contain other materials, affine transformations, ...



Material properties
defaultmaterials
surfaceRoughness         <value>
emissive                 <red> <green> <blue>
ambient                  <red> <green> <blue>
diffuse                  <red> <green> <blue>
specular                 <red> <green> <blue>
specularExponent         <value>
lightBackFaces           <value>
reflectivity             <value>
glossStrength            <value>
transparency             <value>
translucencyStrength     <value>
priority                 <value>
speedOfLight             <value>
retainAmbientColor       <value>
retainDiffuseColor       <value>
retainSpecularReflection <value>
disableRefraction        <value>
	
The specular-exponent must lie in the interval [0,128].
To disable lighting of the back faces of an object use 0 as a value, any other value will enable it. The speed-of-light is expressed as a number between 0 and 1. The priority of an object (used when transparent objects interpenetrate each other) is expressed with an integral number (higher numbers denote higher priorities). The priority of air is 0. To retain a transparent object's ambient color, use a value of 1, the same holds true for its diffuse color and its specular reflection. When rays must travel straight through bodies, use a value of 1 for disableRefraction.



Motion blur
mbClear
mbRotate         <angle> <ux> <uy> <uz>
mbTranslate      <dx> <dy> <dz>
mbScale          <sx> <sy> <sz>
mbEnable
	
A shape's motion blur behaviour is specified a bit different than specifying its affine transformation. Three motion blur methods are supported: rotation, translation, and scaling. Use the mb-prefixed commands to specify which operation you want to perform. Make sure you use the mbEnable keyword to explicitly enable motion blur for the current shape. The final affine transformation will contain first the scaling, secondly the rotation, and third the translation. Note that after a shape is specified, the current motion blur transformations are cleared.



Global scene attributes
eyePoint                 <x> <y> <z>
viewPoint                <x> <y> <z>
viewAngle                <value>
upDirection              <dx> <dy> <dz>
background               <red> <green> <blue>
globalAmbient            <red> <green> <blue>
lightAttenuation         <constant-factor> <linear-factor>
                         <quadratic-factor>
atmosphericAttenuation   <front-plane> <front-scale> <back-plane>
                         <back-scale> <red> <green> <blue>
fog                      <fog-start> <fog-end> <red> <green> <blue>
shadowFeelerEpsilon      <value>
reflectivityEpsilon      <value>
transparencyEpsilon      <value>
meshEpsilon              <value>
adaptedShadowComputation <value>
minReflectivity          <value>
minTransparency          <value>
maxRecursionDepth        <value>
showExtents              <value>
motionBlur               <#frames> <methods>
                         [<final-frame-brightness>]
	
Note that when using fog, a linear equation is used. To disable the showing of extents, use 0 for the value as any other number enables them. All the epsilons are 1E-6 by default and the maximum recursion depth is 5 by default. If necessary, adjust mesh-epsilon so shading is properly computed for difficult meshes. If shadows don't seem correct, use a non-zero value for adaptedShadowComputation. The default value for the frame-length when performing motion blur is 1. The number of time samples taken for each pixel is 8 by default. These time samples are evenly distributed and slightly jittered over the frame length.



Textures
solidTexture      <type> <parameters>
imageTexture      <filename> <method> <tile-s> <tile-t>
proceduralTexture <type> <method> <parameters>
flatTextureOffset <s-offset> <t-offset>
	
The solid-texture type specified (along with its required parameters) can be one of the following:
  • 0: none (default)
  • 1: checkerboard
    • 3 parameters needed:
      • the bars' scales in the X, Y, and Z directions
  • 2: stacked colorbars
    • 3 parameters needed:
      • the bars' scales in the X, Y, and Z directions
  • 3: RGB cubes
    • 3 parameters needed:
      • the cubes' scales in the X, Y, and Z directions
  • 4: contour lines based on one coordinate
    • 2 parameters needed:
      • the coordinate involved (0 = X, 1 = Y, 2 = Z)
      • the scale of the coordinate involved
  • 5: contour lines based on two coordinates
    • 4 parameters needed:
      • the coordinates involved (0 = X and Y, 1 = Y and Z, 2 = X and Z)
      • the type of distance measure to use (0 = Euclid, 1 = Manhattan, 2 = sin*sin, 3 = sin*cos)
      • the scales of the coordinates involved
  • 6: contour lines based on three coordinates
    • 4 parameters needed:
      • the type of distance measure to use (0 = Euclid, 1 = Manhattan, 2 = sin*sin, 3 = sin*cos)
      • the scales of the coordinates involved
  • 7: wood grain
    • 11 parameters needed:
      • the rings' thickness
      • the wobble strength
      • the number of wobbles
      • the twist strength
      • the red, green, and blue components of the first type of rings
      • the red, green, and blue components of the second type of rings
      • a flag to set smooth interpolation between different rings
        (if this flag is 0 only two colors are used, else the rings' colors are smoothly interpolated)
  • 8: marble
    • 5 parameters needed:
      • the coordinate involved (0 = X, 1 = Y, 2 = Z)
      • the turbulence strength (i.e., 5.0)
      • the weights for the red, green, and blue components of the marble
  • 9: cow spots
    • 1 parameter needed:
      • the black/white treshold between 0.0 and 1.0 (i.e., 0.5)
The procedural-texture type specified (along with its required parameters) can be one of the following:
  • 0: none (default)
  • 1: checkerboard
    • 2 parameters needed:
      • the number of tiles in the S and T directions
  • 2: radial colors
    • 6 parameters needed:
      • the red, green, and blue components of the inside color
      • the red, green, and blue components of the outside color



Macros
def <name> { <definition> }
use <name>
	


Including other SDL files
include <filename>
	
After including the specified file, all its definitions, lights, and shapes are available to the current SDL file. No other attributes are inherited.


Galleries Jump to overview menu

C++ Code and Documentation Jump to overview menu

Below, you can find all the code as written in C++. It was tested under Linux (using a Makefile) and with Microsoft Visual C++ 6.0 (OpenGL and GLUT are of course required in both cases). I also included the executables that run under Windows 98/2000/XP. There is also a zipped data tarball which needs to be extracted in the directory where the executables are located (this data directory contains all the textures, meshes, SDL scenes, ... which make up nearly 20 MB). In order to get started, you should read the software package's documentation, which is included below.

The code itself accounts for 22438 lines (666 kB), the scene files and documentation files account for 11760 lines (449 kB). In total there are 144 files that account for 34198 lines (1 MB). Just to give you an idea...

  • The Software Package

    • C++ sourcecode (ZIP, 270 KiB)
    • Miscellaneous datafiles (ZIP, 8.31 MiB)
    • Microsoft Windows 98/2000/XP executables (ZIP, 1.41 MiB)
  • SvenGL Documentation

    • Advanced Computer Graphics using OpenGL (PDF, 296 KiB)
    • You know you've been ray tracing too long when... (PDF, 79 KiB)
  • OpenGL Documentation

    • The OpenGL Graphics System: A Specification (version 1.2.1) (PDF, 1.31 MiB)
    • The OpenGL Utility Toolkit (GLUT) Programming Interface (API version 3) (PDF, 403 KiB)