Friday, February 27, 2009

Assignment #4

This assignment adds texture mapping and antialiasing to the basic raytracer in assignment #3.

There are two texture maps and mapping methods:
  1. A PNG image of the earth mapped to a sphere using a transform from XYZ coordinates to spherical coordinates.
  2. A procedural checkerboard texture map for the plane. The color for the plane is calculated based on the intersection point.

The antialiasing technique used fairly simple. It takes the number of samples requested and generates that number of equally spaced rays through the corresponding pixel. Each ray's color is determined and the colors are averaged

Adding some jitter to the samples will provide better antialiasing. It would have been added to this version of the ray tracer, but too much time was spent rewriting the code from the previous assignment and fighting with C++. It isn't C++'s fault that my coding skills are rusty. That's my excuse for the simplistic antialiasing :)

The antialiasing removed the jaggy edges from the spheres, triangles and checkerboard pattern on the plane. It also made the earth texture less speckled.

The difference between 16 samples/pixel and 128 samples/pixel is subtle. I suspect that if the surfaces were not being modeled as perfectly smooth that the difference between 16 and 128 samples per pixel would be much more noticeable.

Interestingly, the rewrite sped up rendering by 60%. This was very surprising considering the number of abstractions added to the code.


tar jxf jbowles-assign4.tar.bz2
cd jbowles-assign4
make
./raytracer --width=512 --height=512 --reflections \
--shadows --samples=16


Usage: ./raytracer [options]

options:
--width Window width
--height Window height
--reflections turn on reflections
--maxdepth maximum number of reflections
(default 3)
--shadows turn on shadows
--samples square root of the number of
samples per pixel.
--perf turn on performance measurement
rendering

To exit, pressing Apple-Q, escape, or just q.


Output generated on a MacBook Pro 17" 2.4GHz dual core. Size of each image is 512x512. Each image can be clicked on to view a larger 1280x1024 version.

1 sample/pixel:
4 samples/pixel:
16 samples/pixel:
32 samples/pixel:
128 samples/pixel:

Alternate viewpoints:





Render times:
Samples 512x512 render time (s) 1280x1024 render time (s)
1 0.323498 1.716881
4 1.189341 6.245954
16 4.617202 25.190776
32 7.222273 39.810833
128 36.107344 188.703291

Friday, February 13, 2009

Assignment #3

Assignment #3 - basic ray tracer. Features:
  • Diffuse lighting
  • Ambient lighting
  • Specular highlights
  • Shadows
  • Reflections
  • Simple anitialiasing
  • High dynamic range color
  • Geometric objects: spheres, planes, triangles
  • Perspective is maintained regardless of requested window size - pixels remain "square"
How to build and run:
Platform: Mac OS X 10.5.6, XCode 3.0 / gcc 4.0.1

tar jxf jbowles-assign3.tar.bz2
cd jbowles-assign3
make
./raytracer --width=512 --height=512 --reflections \
--shadows --samples=3

Usage: ./raytracer [options]

options:
--width Window width
--height Window height
--reflections turn on reflections
--maxdepth maximum number of reflections
(default 3)
--shadows turn on shadows
--samples square root of the number of
samples per pixel.
--perf turn on performance measurement
rendering

To exit, pressing Apple-Q, escape, or just q.

Output generated on a MacBook Pro 17" 2.4GHz dual core. Size of each image is 512x512.

./raytracer --width=512 --height=512
1.114634 seconds to render
Number of rays: 262144



./raytracer --width=512 --height=512 --shadows
1.161857 seconds to render
Number of rays: 446464



./raytracer --width=512 --height=512 --shadows --reflections
1.270463 seconds to render
Number of rays: 693328



./raytracer --width=512 --height=512 --shadows \
--reflections --samples=5
32.375095 seconds to render
Number of rays: 17334027

Optimizations

It was time to optimize my raytracer and gprof turned up something unexpected: most of the time was being spent in vec3f constructors. I expected most of the time to be spent checking for intersections. It was time to turn on gcc optimizations.

The result: the constructor calls disappear after passing either a -O or -O1. Best performance was to be had at -O3.

This raytracer is being compiled on a MacBook Pro 17", 2.4GHz Intel Core 2 Duo with 4GB of RAM. gcc:

gcc -v
Using built-in specs.
Target: i686-apple-darwin9
Configured with: /var/tmp/gcc/gcc-5465~16/src/configure --disable-checking -enable-werror --prefix=/usr --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-transform-name=/^[cg][^.-]*$/s/$/-4.0/ --with-gxx-include-dir=/include/c++/4.0.0 --with-slibdir=/usr/lib --build=i686-apple-darwin9 --with-arch=apple --with-tune=generic --host=i686-apple-darwin9 --target=i686-apple-darwin9
Thread model: posix
gcc version 4.0.1 (Apple Inc. build 5465)

Image size: 512x512, 1 plane, two spheres, 1 triangle. Average run times for 5 runs each:
Average Run Time Optimization Debugging and profiling
1. 8.1604598 None yes
2. 2.0264776 -O yes
3. 2.036941 -O1 yes
4. 2.2078138 -O2 yes
5. 1.2405112 -O3 yes
6. 1.2331454 -O4 yes
7. 1.1143894 -O3 no
8. 1.1208828 -O6 no
9. 1.1019208 -O4 no

Notes:

  1. 1. gprof shows that most of the time is spent in vec3f constructors

  2. 2. gprof shows that most of the time is spent in sphere and plane intersections,
    but I still see more calls than expected to vec3f constructors

  3. 3. This generates a different binary than the previous test and runs at approximately
    the same speed. Still more vec3f constructor calls than expected.

  4. 4. -O2 yields no real change from -O1

  5. 5. gprof yields expected results, big speedup from -O1 and -O2

  6. 6. Seems to be the same as -O4

  7. 7. Gained 1/10th of a second from dropping debugging and profiling

  8. 8. -O6 shows no gain over -O3

  9. 9. -O4 without debugging and profiling is 1/100th of a second faster than -O3, but that isn't big enough of a change to say that something changed. There could just be less load on this system.

Tuesday, January 27, 2009

Image Viewer

Assignment 1: Viewer, due Jan 27.

  • Use OpenGL and Glut or Java and Swing to produce a simple image viewer.
  • Viewer must accept command-line parameters for size, camera, lights, etc...
  • Viewer must report pixel values under mouse when image is clicked on...
  • Viewer must report current state (size, camera...) in command line form when 'C' key is pressed
  • Viewer must display an image either read from file or generated by the program.
  • Extra credit 1: measure speed of image updates. (how fast can images be loaded?)
  • Extra credit 2: identify fastest method for image updates. (use GL extensions)

Usage


Usage: ./image_viewer [options]

options:
--filename=[filename.png] File to load
--float Use floating point color
--width Window width. Defaults to image
width
--height Window height. Defaults to image
height
--drawpixels Use glDrawPixels to draw image.
Default is to draw textured quad
--eye (x,y,z) eye location
--center (x,y,z) where the eye is looking
--up (x,y,z) vector for normal to
the eye's plane
--light0 define light 0
--light1 define light 1
--light2 define light 2
--light3 define light 3
--light4 define light 4
--light5 define light 5
--light6 define light 6
--light7 define light 7
--light8 define light 8
--light9 define light 9

To exit, pressing Apple-Q, escape, or just q.


Different Implementations and Effects on Speed


I implemented this project two different ways:

  1. Read the image into a buffer and use glDrawPixels to put the image onto the display.
  2. Read the image into a buffer and draw a textured quad.


Timing the glDrawPixels implementation:
0.019059 seconds for first display()
0.005490 seconds average for display()

Timing the textured quad implementation:
0.009020 seconds for first display()
0.000642 seconds average for display()

Using a textured quad is far faster than using glDrawPixels. Since we'd like to display frames as fast as possible, this is important to know.

Interestingly, the first time through the display function always took a lot longer. This probably has something to do with OpenGL initialization.



One thing I would like to look into is using glAsync or the ARB_sync extension so that I can write to a buffer while rendering it. This may prove useful for ray tracing.

Informal Discussion of Development Process


This program uses OpenGL and GLUT to display the image. It is rendered using a textured quad. The camera uses the simple orthographic projection.

I hadn't written any OpenGL and GLUT code in at least 10 years. This assignment required me to bring out my old rusty skills.

My plan was to load the image, bind it to a texture and display a textured quad. First I just wanted to get a white quad up on a black background, but got distracted trying to link to libpng.

I messed around with the XCode IDE a bit and couldn't figure out how to get it to link to the PNG library that codes with XCode. There were plenty of examples of how to link to a PNG library that you compiled and installed in /usr/local, but I didn't think my instructor would appreciate me requiring him to do the same. Those examples didn't to work when I pointed them at the libpng installed with XCode. I was probably just doing something stupid, but I was getting tired and frustrated. I just wanted to code!

So I busted out an old Makefile and pointed the linker at /Developer/SDKs/MacOSX10.5.sdk/usr/X11/lib where libpng lives. It was simple. Now I was able to link to libpng.

Next I built a simple program using GLUT and OpenGL that displayed a white quad on a black background.

Now I needed to load a PNG. This took a while. First I started reading the libpng documentation, but it wasn't really clear how to load a PNG. The documentation tends to, er, wander and expects that you know a lot the PNG format. I just wanted to load an image, dammit! How hard would it be for them to provide a function that takes a file handle and loads the image?

In their defense, PNGs need to be loaded by some pretty complex programs and need a complex API. I'm writing a simple program and I bet that I am not their target consumer. If it really pisses me off that, maybe I should contribute some code? Hmmm...

So I looked online for some examples. Most sucked. I finally found some code written by David Henry. I compiled it and it worked! Well, mostly. The image had some stair-stepping issues.

I extracted his PNG reading code, refactored it into C++, moved the global variables into my new PNGTexture object and added exception handling. Then I textured my quad, and it looked great, if you didn't mind a little stair stepping.

At first I thought the problem was with the PNG loading code. I spent a lot of reading through this code and decided that I really didn't care about the mechanics of loading a PNG file. I'm much more interested in ray tracing, which is what this class is about. I built a checkerboard texture in my code and used that instead.

It worked fine. So the texture coordinates were correct. I should have done this in the first place.

Since I didn't want to understand the loading of PNGs, I looked at the code that loaded the texture into the OpenGL state machine. I compared it with an example from the Red Book and found that I was missing a call to glPixelStorei(GL_UNPACK_ALIGNMENT, 1).

This call was missing from the PNG loading code.

So, I added that call and the stair-stepping problem disappeared. I was loading PNGs and not lame PPMs!

Now I just needed to get the color of the pixel underneath the cursor. This is simple. Just use glReadPixels. Then I added the state printing and started timng the display function.