Showing posts with label cuda. Show all posts
Showing posts with label cuda. Show all posts

Tuesday, October 14, 2008

Raytracing for Dummies

A little while ago, I posted about NVidia's CUDA; this is a follow-up on what I've been doing with it.

Most people, I noticed, seem to start out by writing yet another Matrix Multiplication for parallel processing. I don't know about the rest of you, but I never seem to need a faster matrix multiplication... Heck, I rarely even need a slow one.

So instead, I decided to write a raytracer. I wrote one in Java just for fun a few months ago, so this isn't completely out of the blue; Java, simply by being Java, is not exactly the fastest language for rendering. So this time I decided to focus on performance, and add visual features after I was happy with that.

About the title of this post: I plan to describe how a raytracer works from a programmer's perspective. Here's where that starts:

Raytracing is done by shooting rays from the viewer (camera) into the scene to see what they hit. An image is generated by using the results of these ray-object intersections to color pixels.

To make things more complicated, at each ray-object intersection, multiple rays may be shot in other directions to detect light sources, reflections, and refractions. All of these rays return color data, which is somehow combined into a single color for a pixel.

This is the kind of description that you can find anywhere. The problem that I have with it is that there are no details. How do you shoot the rays? What directions? How do you combine the colors?

I'm going to answer some of this, and I'm going to be specific.

Firstly, you need to understand how everything is laid out. To start, you will need a camera and a viewport. Think of the camera as the eye of the person viewing your scene, and the viewport as the screen displaying the image.

You will need the following data: Position of the camera (x, y, z), direction the camera is facing (x, y, z), distance from the camera to the viewport, dimensions of the viewport (width, height) both in pixels and in scene units.

Scene units? Your scene should be defined using floating-point coordinates in 3-space. You can choose whatever size these units should represent, just be consistant within your scene. I like to think of 1 unit = 1 foot.

Now what about shooting those rays into the scene? This is actually relatively simple now: Shoot one ray from the camera through each pixel of the viewport. That will give you a starting point and a direction, which is exactly how rays are defined. Then all you need to do is search your scene objects for intersections.

This involves a significant amount of 3-D trigonometry, however, which wasn't exactly stressed in my High School. But I've figured out some calculations that should help:

Firstly, you need to imagine your viewing plane as being a screen in front of the camera. You need to know the positions of each of the pixels in this screen so you can determine the directions for all of your rays. Here is how to compute the position of each pixel:

Variables used here that you must define (a vector has .x, .y, and sometimes .z coordinates):

  • cameraPos = The position of the camera; a floating-point 3-D vector
  • cameraDir = A point that the camera is looking at directly; a floating-point 3-D vector
  • cameraDist = The distance between the camera and the viewport; a floating-point number
  • viewportSize = The size of the viewport in scene units; a floating-point 2-D vector
  • screenSize = The size of the screen in pixels; an integral 2-D vector

The function 'normalize()' on Vector3 objects returns a vector pointing in the same direction that is 1 unit long.

So if anyone is trying to write their own raytracer, grab that 'angle' variable and use it as the direction of each ray you try to shoot.

Lighting is one of the simplest features you can add to a raytracer. Each time a ray hits an object, loop through all of your light sources and try to reach it from the point where the ray hit the object; if you can reach the light source without striking another object in between (including the one that the original ray hit), that point is lit by that light source.

One thing to be careful of, though: Any time you have a hitpoint and you want to shoot a new ray from there, add just a little bit of the normal for the surface at that hitpoint to the starting point for the new ray, so you don't immediately strike the object you are bouncing off of.

Here is a simple ray-object intersection equation that you may find useful; it is for a sphere, and was originally taken from the WildMagic foundation library of intersections (GPL code):

Requires:

  • center = center of the sphere; floating-point 3-D vector
  • radius = radius of the sphere; floating-point number
  • origin = starting point of the ray; floating-point 3-D vector
  • direction = direction of the ray; floating-point 3-D vector

Outputs:

  • hitpoint: the first point where the ray hits the sphere, or null if it does not; floating-point 3-D vector
  • normal: the normal of the sphere surface where the ray struck it; floating-point 3-D vector

A note about ZERO_TOLERANCE: Since floating-point calculations are not exact (if you have variables A and B which both contain numeric results that should be equal but were achieved using different calculations, A == B may come back false), ZERO_TOLERANCE is used to put a small boundary around 0 that is considered equal to 0. You should probably set it to something like 0.00001.

Combining colors is a much more difficult concept, I've discovered. Here is what I have been doing, although I am not satisfied with it:

Colors are represented as Vector3 objects; x = red, y = green, z = blue. Each color channel should be in the range [0, 1] with 0 = 0 and 1 = 255.

I simply add the color of each object struck by the ray to the colors of any lights shining on those hitpoints, and either clamp (bound x, y, and z to the range [0, 1] independently of each other), or scale (scale all components back by the largest one, if any are above 1) the colors. I haven't found a better way to do this yet, although I am planning to try scaling back the influence of each color as I traverse more and more rays to reach it.

Hope I got someone interested in this stuff; it's a cool way to generate 3-D scenes.

NVidia CUDA

The other day I picked up NVidia's CUDA (Compute Unified Device Architecture, which I must say is a pretty bad acronym) to have something new to play with. (Some people tinker with car engines, I tinker with software packages... I know, I'm a little odd.) I've always had an interest in parallel programming, so CUDA seemed right up my alley.

Then I tried to install it. Maybe others have had an easier time of it, but my experience was less than top-notch. I run a dual-boot system at home, Windows XP Pro and KUbuntu 8.04.

Windows

I had no trouble at all installing CUDA on Windows, but the compiler that comes with it enforces a requirement for version 7.1 or 8.0 of Microsoft's compiler (Visual Studio 2003 or 2005, respectively). I don't have either of those, and it didn't seem worth hunting down a copy, so I gave up at that point.

Linux

Being created by and for software developers, I've found that the one thing you can always count on in Linux is the C compiler. It never seems to break.

Unfortunately, drivers are another issue. CUDA requires a new version of NVidia's device drivers, and the drivers require a new version of the Kernel module that supports them. I must have installed the drivers four or five times, always getting the same failure back from the device query program that comes with the CUDA SDK. It kept reporting that the driver was version 173.something, and the kernel module was version 167.someotherthing.

I fought with it for a day and a half, only to discover from a forum post that I needed to add "nv" to the list of disabled kernel modules in some /etc file that I'd never seen before. And suddenly it worked! I love that feeling of accomplishment when something suddenly works, even if I needed help. Sort of like when you finally kill the mosquito that bit you three times and has been buzzing in your ear for half an hour.

So expect some updates soon about how my CUDA programming is coming.