Triangle Rasterizer
Home Up Research Ultimate List

The assignment is:

In this assignment you are to implement a triangle scan conversion routine and Z-buffer hidden surface algorithm. 

Your method should take a vector of 3 double-precision X-Y coordinates for the vertices in screen coordinates. Each vertex should also have an associated Z value (actually 1/Z) in double precision, and a color value expressed as 3 bytes giving red, green, and blue values.

Only sample the triangle at pixel centers (you decide where the center is relative to the pixel coordinate system). Linearly interpolate 1/Z, R, G, and B across the triangle.

Your method does not have to be particularly fast (optimization is fun but not part of this exercise) but I do want you to think about how you would optimize for either 1) large triangles (> 50 pixels) or 2) tiny triangles (~ 1 pixel). You don't have to do both cases, just choose one.

Feel free to look at code from the web or from books but do not copy code. You are responsible for making sure you understand your code. You can implement your routine in any programming language.

Set up a linear model describing the time behavior of your algorithm and determine the parameters of your model. For example, a routine optimized for large triangles probably has some "setup" code, some "per scanline" code, and some "per pixel" code. So my model would be something like Ttotal = Tsetup + NLines * Tline + NPixels * Tpixel. I'd run large numbers of triangles of differing sizes and collect total times using whatever timer is available. Then I'd solve the equations for the unknown parameters. Finally, I'd check the residual of my equations to see how well I did. I would have to experimentally adjust the number of triangles in each timing run to be sure I got enough timing accuracy.

My scan conversion routine iterates through the pixels in the triangles bounding box and tests edge equations.  This means that it is sensitive to the winding of the triangles.  In its current implementation it actually follows a left-hand rule.  I realize this is unconventional, but I didn't figure this out until after I had it working and decided that it didn't really matter since it could easily be reversed.  So triangles whose vertices arrive in a counterclockwise order will not be displayed.  To make the algorithm insensitive to the winding order, the test would be changed to consider a pixel inside the triangle if the edge equations all evaluate to the same sign at the pixel center.  Note that you would not use the winding at this point to implement back-face culling because it is too deep in the pipeline.

The implementation solves for the normalized barycentric coordinates of a pixel and interpolates 1/Z.  If the z-test passes, then the color is interpolated and the R, G, B, and 1/Z values of the buffer are updated.  After all triangles have been converted, the buffer is thrown onto the screen by a call to glDrawPixels().

Here is a screenshot of a bunch of random triangles:

As you can see, the z-buffer that I implemented takes care of the hidden surface problem for this triangle mess.

My algorithm is poorly optimized for large triangles.  Each pixel in the bounding box is treated individually; there is no use of coherence.  An algorithm better suited for large triangles would use an edge table that is updated (but usually unchanged) every scan line.  At each line the span covered by the triangle would be solved for explicitly.  Then, interpolation could be sped up by using deltas for R,G,B, 1/Z for each pixel in the span.  This method would not be well suited for small triangles though because all the setup calculations would be more expensive that just treating each pixel independently if the number of pixels is sufficiently small.

I could not come up with a good linear approximation of the time.  I used Matlab to find the least squares solution to the following equation:

T = Tpixel * Npixel + Ttriangle * Ntriangle + Tbase

I left out a per-line component because my algorithm does not do anything between lines except increment the line counter and reset the column counter.  I added Tbase to account for function call overhead and timer overhead that is constant regardless of the number of triangles and pixels rendered.  This didn't help, however.  Ttriangle and Tbase were both negative.  I expected them to both be nearly zero and Tpixel to be a small positive number.  This is because my test cases rendered similar triangles at random positions on the screen.  Therefore, the percentage of pixels in the bounding box for each triangle is the same.  After using different values for the size and number I was unable to get a nearly linear result.  I found this quite surprising.  I suspect there must be another factor that is skewing the results such as cache usage.  Initially, I suspected that my test cases involving very large triangles or high triangle counts were the cause (because of occlusion), but this did not appear to be the case.

The actual result is:

T = 1.0365e-6 * Npixel - 9.0247e-7 * Ntriangle - 0.0081357

Here is an image from a test case: