Log in

No account? Create an account
Apr. 7th, 2011 @ 01:33 am Raytracin'
About this Entry
Spinning Box
So, I was complaining a little while ago that, despite not over-using trigonometry, I was still getting distorted results with my raytracer. No longer! I really want to write a document on how to make 3D graphics effectively, but I suppose a blog post will do:

Firstly, raytracing is the process of creating a picture plane in space. Imagine looking at a picture-frame on a wall, and imagine that there is no picture within the frame-- just blackness. Now, hold an apple between your face and the picture-frame on the wall. If, when you removed the apple, the picture of the apple appeared on the matte within the frame-- you'd have accomplished in real life what I'm trying to do on a computer.

The idea is tracing light-rays from your eye to all the points within the picture frame. We want to trace light rays in straight lines to an imaginary grid on the picture-frame that corresponds to pixels on your screen. I suppose it's here that our analogy ends. In olden times, I tried sweeping an arc using some heavy trig for each point, creating an essentially-hyperbolic "plane" (I thought this would be a brute-force mechanism)... making a mess of all the projections, but that's not important here.

Back to the point! We've got this picture-frame plane. We'll call the top-left corner T, the top-right U, the bottom left V and the bottom right is W. Each point has three dimensions: X, Y, and Z. For my 'tracer, I'm choosing Z to be the up-axis, but that's kinda non-standard. We'll define the points in respect to the camera. The angle specifying the width of your vision is called the field of view (FOV). Humans can actually see about 170 or so degrees, but our picture won't take up this entire "natural" field of view. I set my default FOV to around 90. The next parameter is distance (D). How far is the picture frame from your face? It doesn't actually make much difference-- since the picture frame we're creating will get scale with distance. It's just a matter of how much space you'll need for your scene. I just assume the units to be meters, but it doesn't matter. Feet are cool, too. Next, it would suck having to move the whole scene around just to look at it, so we'll make the camera able to be moved and pivoted. The camera (C) has three dimensions, too. The pivots I'm calling pitch (looking up and down) and yaw (turning side-to-side).

With all our intermediate parameters defined, we'll start with the trig. We need to define T, U, V, and W in terms of where the camera is, so we can draw straight lines to them and the points within the plane they define. Recall that when you rotate a point around the origin, the x-value is the cosine of the angle, and the y is the sine. To rotate in 3D is two 2D rotations-- the first with the pitch and the second with the yaw. Imagine looking at the side of a car trunk opening. It pivots up-- the pitch increases. If we wanted to adjust the yaw of a point from it, we'd look down from the top, find a point and twist it around using the same math from the new angle. Since we'll be using this math for each of the four points, we'll make it into a function:

Function g(pitch, yaw)
Let l = D*cos(pitch)
Let z = D*sin(pitch)
Let x = l*cos(yaw)
Let y = l*sin(yaw)
return (C.x + x, C.y + y, C.z + z)

Pretty simple, no? Just two transformations using basic trig. Note that in the function, we're passing into it values of "pitch" and "yaw", not the camera's. The notation "C.x" just means the x value of the camera C. D is the distance and l is a remnant from the first rotation. So, let's define some points:

T = g( C.pitch + (FOV*AR)/2, C.yaw - FOV/2 )
U = g( C.pitch + (FOV*AR)/2, C.yaw + FOV/2 )
V = g( C.pitch - (FOV*AR)/2, C.yaw - FOV/2 )
W = g( C.pitch - (FOV*AR)/2, C.yaw + FOV/2 )

Here's a little surprise I forgot to mention: Aspect Ratio! Since the picture-frame (your screen) isn't a square, we have to do a little multiplication. The width of your screen divided by its height is called the "aspect ratio". AR here is actually the inverse of the aspect ratio. We're just using it to muckle with the field of view. Nonetheless, we're using the "g" function above to diddle with the camera and distance parameters to define the picture frame. You'll notice that D is in the definition of g(pitch, yaw), so the rotations (and thus the size) of the frame will get bigger with the distance.

The last part is actually defining the lines that are calculated. Luckily, if you can think in one dimension (a straight line), you can think in three-space. The idea's simple-- look at this equation below:

Pi = T + ( -i/width )(U-T)

Since T and U are defined, they are constant. "width" is a new constant I'm throwing in representing the width of your frame. So, we have a function with one independent variable (i) and Pi is dependent on it. When i is zero, (-i/width)(U-T) equals zero as well, then Pi is simply T. When i is equal to "width", (i/width) equals one and Pi is equal to U. Intermediate values will find points between T and U. Algorithmically speaking, we need to repeat this for all values from i = 0 to width, and then do something analogous to draw lines from T to V and U to W. (If you doodle this, it makes more sense).

Algorithmically, to "shoot rays" at all the points on the grid starting at the top left and moving across and down, we'd say:

for each j from 0 to height:
   Pj1 = T + (V-T)(-j/height)
   Pj2 = U + (W-U)(-j/height)
   for each i from 0 to width:
      Pij = Pj + (W-U)(-i/width)
      shootRay(C, Pij)

shootRay works pretty much under the same formula. Just does collision detection for any points between the camera and point Pij. So, to cast a point from source S to destination D with ray resolution R, it would look like this (unsurprisingly, if you've been following along):

testPoint = S + (D-S)(-k/RR) ... for k is 0 to RR

So, if the scene contains something at testPoint, then the frame will contain the color of that object at point (i,j).

Recall that to add two points together, you'll have to add the X, Y and Z components of each. So, when I say D-S I mean D.x - S.x, D.y - S.y, and D.z - S.z. (Technically, all the points are called "vectors", but this is supposed to be trig-level).

Anyways, that was more detail than I really wanted to expose, but I'm pretty happy about it. Anybody can really write a raytracer. If you're interested in why mine was giving me crap was 'coz instead of using Pj2 above, I was tracing lines from Pj1 across to U each iteration... which gets more and more slanty as the render progresses. Also, I kinda lied about aspect ratio working so easily. Genuinely, it takes more finagling to get a perfect circle render on a rectangle screen.

The actual program is pretty useless right now. It'll render basic shapes with basic colors and it has an interactive console and stuff... but stay tuned. At some point, it'll probably have a sourceforge page and a nice LaTeX-ian tutorial and manual.

But that's all for some other time
Date:April 8th, 2011 07:46 pm (UTC)
(Permanent Link)
Your site article is very intersting as well as fanstic,at the same time your blog theme is exclusive and ideal,great job.To your success.

[User Picture Icon]
Date:April 9th, 2011 07:14 pm (UTC)


(Permanent Link)
I had no idea that "...when you rotate a point around the origin, the x-value is the cosine of the angle, and the y is the sine."

I read through this and didn't really get it. But then again, I'm not as mathematically inclined as you are... =/
[User Picture Icon]
Date:April 9th, 2011 07:15 pm (UTC)
(Permanent Link)
But you're definitely a good tutorial writer.
You should write computer books.