GPU ray tracing
From GameStudio Wiki
Ray Tracing graphics in 3dgs
This article presents several useful pieces of information regarding ray tracing in general and its implementation in 3D Gamestudio. This article assumes the reader has an advanced understanding of 3D computer graphics, trigonometry, and multi-dimensional algebra (matrices and such).
As computers become increasingly powerful, new and more advanced rendering techniques that were once considered impracticle are now becoming integrated within current generation computer games and graphics. In this article, ray tracing, an alternative method of rendering graphics by tracing rays throughout a scene, is introduced. Many graphics companies are researching both cpu and gpu ray tracing, however, in this article, we will stick with gpu ray tracing.
So why use ray tracing? Well... ray tracing is a more similar to the way light actually interacts with objects in real life. If you have a lamp on your desk, the lamp's bulb is emitting an infinite or very large (depending on which scientific theory) number of rays which travel at the speed of light and bounce around the room until they are fully absorbed by materials due to collision. On the computer, we cannot simulate this many rays yet, so instead we generate a minimum number of rays to represent the scene up to an acceptable level. Pure ray tracers work by ONLY using ray tracing. For the first step, these engines will shoot one ray for every pixel on the screen from the screen (camera) into the scene. Each ray is then followed for 'n' number of reflections, refractions, and collisions until the rendering is compiled. In 3dgs, this is impractical because we already have many nice rasterization techniques built into the engine.
Instead, we can skip the first step of tracing rays from the screen (camera) out and use rasterization for the first step (iteration) of ray tracing. So why don't we just use rasterization for the rest of the steps? The answer is that once the rays hit their first objects, they are no longer traveling in a predictable direction. The first set of rays from an orthogonal view are all parallel in direction (or a perspective camera, the rays will all "spread out" evenly as a function of the view's aspect (arc)). Once the rays are reflected and refracted, we cannot capture the ray tracing data in a single view. The number of views required to render a rasterized "fake raytracing" scene would be the total number of pixels times each iteration minus one calculated plus one(the first iteration only needs on view). This is not practical at all.
In adddition, rays must be casted into the scene from light sources to create proper lighting and shadowing. This is best done entirely in ray tracing as a shadow mapping first step would create the same aliasing problems found in normal rasterization.
--Implementation--
In 3dgs, we can make use of pixel and vertex shaders to create very fast but simple gpu ray tracers that will run using vs/ps 3.0 or higher.
First, we set up a view, we will use the camera in this example. We set a material to the camera that renders the entire scene with our ray tracing fx file. Believe it or not, this is all we need to do to start!
Inside of our ray tracing effect (.fx) file, we will need several things: -a ray struct - I use a float3 for world position and another float3 for normalized direction (velocity) -a collection of structs for different shaped objects (I made 5, cubes, planes (quads), polygons, spheres, cylinders) -a fragment shader to calculate collisions between each of our object types and our ray struct -a tree which will hold any number of objects (I use a KD architecture but octree or even just a list will do)
-our VS shader -out PS shader -the technique
The Vertex Shader should be basically just a normal rasterization shader outputing the scene depth and normals to the pixel shader.
One thing I thought of doing in this stage is doing a preliminary search into our object tree which will greatly increase framerate and overall preformance. Because the rays will all reflect in similar ways off of a single normal, we can basically trace out from the screen and at least cull all of the objects that are not reachable within a function of visibility. I use a linear search to remove any object that lies behind the previously closest object, similar to culling in rasterization. I then shoot a ray between all objects that were removed and replace them if any of the lines intersect the difference between the two closest objects, thus filling a room with objects while keeping walls or floors as level boundries.
Once this step is completed, we begin the Pixel Shader were we do the heavy work. For each ray, for each iteration, we update the ray and check for collision with the object tree by calling the object's corisponding collision function. If the object is hit, we update a temporary variable that stores the closest hit. when all objects are checked for collision, we can look up the texture(s) from the object and update the ray. simply reflect the ray around the collision point's normal or use a refraction equation if the material (which can be stored in textures) refracts light. We can also make materials that scatter light (subsurface scattering) or stop light (like a black hole or a very matte surface). Once the ray is updated, we store the color/result from the trace so that reflections are properly formed. This step is repeated in a loop until the maximum number of steps have been preformed or the ray has been stopped.
In my implementation of ray tracing, I also included dynamic lighting and shadowing. This is similar to the above step but with a few differences. For one, we start the rays from each light source (for each pixel we trace an additional ray from each light source). We can also reverse the process and start from the pixel and trace outwards to see how many lights can be seen (this produces radiosity lighting). The collision function can also be altered to provide "close" collisions which would cause soft shadows. I also added a final function that takes the ray's travel distance and divides the result by it to make shadows seem lighter if the pixel is less occluded. I also trace a few rays from the source pixel in random directions to produce ambient occlusion.
Before you finish reading and never try this, you should know that ray tracing (or at least my shader) runs at 20 fps on a 1440x900 pixel screen with 2GB ram and a 8700M GTS laptop. Even though this sounds like it would NEVER run, it is surprisingly fast! Don't believe me? Try it out or download a nifty demo at Nvidia's developer zone.
I hope you found this article useful and not TOO boring ;) -Michael H. Auerbach- a.k.a. Foxfire
