I think that writing a ray tracer is a right-of-passage for graphics students. This page includes a few (old) blog entries (and source code) for a ray tracer I wrote using C#.
First iteration of the ray tracer
The first image is a simple triangle and sphere set against a crimson plane with diffuse lighting and a single light in the scene. The second image is a white plane with three colored lights set directly in front of the it. The third image is a box made of six colored planes and a model of a wooden fork (with a single light in the scene). All images are at 640×480.
Ray tracer with shadows, reflection, and refraction
This version demonstrates hard shadows, reflection, refraction, and Phong Shading.
The first image is a set of 4 spheres and a refractive face (2 triangles with a refractive index of 1.3) within a box of 6 planes and 2 lights in the scene. The second image kinda represents a mini-solar system (with a very small sun) on an olive drab plane with 3 lights in the scene. The third image traces 2 spheres and a refractive rectangular face (refractive index of 1.1) near the intersection of 2 planes with single light in the scene.
Comparing a scene with different color methods
I thought it’d be interesting to flip through a single scene and compare the output of each color method. The methods are WithoutLighting, WithLighting, WithHardShadows, WithReflection, and WithRefraction (where each subsequent method incorporates the previous methods). Here goes:
Before going any further, here’s the source code.
Running the Application
After extracting the zip file, you’ll find two directories: Executable and Source Code. Pre-compiled binaries are in the Executable directory, and the source code is in the, errr… you get the picture.
Again, this project was built against XNA, so to run the executable you’ll need to install the following software:
To compile the project, you’ll need:
You can use the keyboard and/or an Xbox 360 Game Pad to control the application. The controls are as follows:
|W, A, S, D||Keyboard||Move the camera Forward, Left, Back, and Right respectively.|
|Print Screen||Keyboard||Take a screen shot.|
|R||Keyboard||Reload the scene from an Xml file (if the scene was initially loaded with an Xml file). Note: this version of the project auto loads changes to the Xml file. As such, you don’t really need ‘R’, but it can be used as a ‘Reset’ feature.|
|Left Thumbstick||Game Pad||Move the position of the camera along the X and Y axes (target stays fixed).|
|Triggers||Game Pad||Move the position of the camera along the Z axis.|
|Right Thumbstick||Game Pad||Move the camera’s target along the X and Y axes.|
|X Button||Game Pad||Toggle the ray tracing coloring methods. Theses methods are WithoutLighting, WithLighting, WithHardShadows, WithReflection, and WithRefraction.|
The are three assemblies that make up the application. These are:
|Bespoke.Games.Framework||This is a set of basic classes I use when working in XNA.|
|Bespoke.Games.Framework.RayTracing||Class libraries specific to the ray tracing project. This is where the bulk of code resides for this project.|
|BasicRayTracing||The executable and host of the Game-derived class. There’s very little actual code here — just initialization and the main update and rendering loops.|
|RayCamera||The camera within a ray tracing scene. The Initialize() method generates and caches a set of direction vectors for casting rays into the scene.|
|Maintains the list of shapes and actors within the scene and implements the Ray Tracing algorithm within its Draw method.|
|SceneLoading\*.*||All of these classes support the Xml scene loading. The entry point to Xml scene loading is the class RaySceneLoader.|
IShape / Sphere / Plane / Triangle / ShapeBase
|Procedurally created “simple” shapes.|
|“Complex” shapes made from 3D models. Actors use the XNA content pipeline to extract the index and vertex buffers to produce a set of Triangle objects.|
So now to the code itself. Of the above significant classes, the RaySceneManager is where the ray tracing is actually happening — starting with the Draw() method. Essentially, with each pass through the Draw() method we build up a Texture2D object by casting a set of rays into the scene, collecting a color at the closest object intersection (or the background color if there is no intersection). We then draw the resulting texture to the screen. Here’s that code (with some timing code removed):
The ray direction vectors are precalculated in the RayCamera.Initialize() method. Most of the work here is handed off to the TraceRay() method, and it’s this method that returns a color to be stored in the texture to be rendered. The TraceRay() method casts a ray into the scene and determines a resulting color using a variety of methods. I’ve used a delegate to allow for different color retrieval methods within the same coding architecture (for effects such as shadows and refraction). Here’s the general TraceRay() algorithm:
- Find the closest “simple” shape object that the ray intersects.
- Find any closer shapes contained within “complex” objects (Actor instances) that the ray intersects.
- With the closest intersected shape in hand, get a color at that intersection point and return it to the calling method.
This third step looks something like:
Where sCurrentColorMethodHandler is a delegate pointing to the currently selected color method.
I’ve been typing on this for awhile now, so I think I’ll call it quits for the moment and let you experiment with the code. I’ve included a set of scenes you can get started with, and you’re welcome to use this code in any way that you see fit.
Please note, there are a number of known deficiencies with this implementation. These include: no refraction for solid objects (e.g. spheres), slow uniform grid initialization for the uniform spatial subdivision algorithm, ray initialization could be improved, and in-code documentation. And I’m certain there are dozens upon dozens of other problems that I don’t know about.
Here’s a screenshot from a fairly complex scene (17,000 shapes) using uniform spatial subdivision:
Ray Tracing Materials
This iteration of the C#/XNA ray tracing project includes more realistic modeling of materials. Specifically, I’ve implemented Fresnel Reflections, and diffuse and specular lighting with microfacet models Oren-Nayar and Cook-Torrance respectively.
These settings are included in the Xml scene definition as in the following example:
center=”0, 0, 0″
The cookTorranceSteepness and orenNayarRoughness values describe the same concept — the degree to which the microfacet normals differ from the surface normal. I’ve separated them so that these values can be modified independently. The microfacetDistribution value indicates the model used for distributing the microfacets around the half-angle (the angle between the incident light and the viewer — this applies to the Cook-Torrance model — and the value returned, from the specified distribution, is the fraction of microfacets with normal pointing along the direction of the half-angle). I’ve implemented Beckmann’s and Blinn’s distribution models.
The diffuse value is still valid (and the specular is derived from this value 1-diffuse) and acts as a coefficient to the corresponding reflectance model.
Here are a few screenshots:
The first image is of a purely diffuse surface without fresnel reflection or microfacets. The second is the same object with fresnel reflection and using Oren-Nayar’s diffuse reflection model with a microfacet roughness of 1.0. Note, that it’s this roughness that causes the flat shape (a commonly used example is the full moon). The third image is a semi-diffuse surface with a specular component, and the last are comparisons of a scene with and without microfacet modeling.
Here’s the updated source code . I believe the implementation is accurate, but if anyone spots a problem please let me know.