CS 6610 Advanced Computer Graphics - Project 4
Zach Gildersleeve
November 20, 2006
CADE login: gildersl
Graduate level credit
Program Description
This program demonstrates the use of OpenGL shaders, written in GLSL, to produce textured, lit objects with bumpmaps. A single quad is drawn as a floor, and is texture and bump mapped in object space using a GLSL vertex/fragment shader, and lit using a diffuse directional light source in that same shader. A single gluCylinder is drawn, and using a second GLSL vertex/fragment shader, the cylinder is smoothly tapered closed on each end, and made to wiggle by perturbing each vertex according to a sine function. The vertex program passes the converted tangent space eye and light vectors to the fragment shader, where the fragments are textured, bump mapped, and lit using the Blinn/Phong half vector specular lighting model. A GUI implemented in GLUI provides control and functionality.
Source Code
The souce code, both the Xcode files and the Visual Studio files, can be found here (22.94MB zip).
Development Platform
This project was coded in C++ and GLSL in Xcode on OS X (10.4.7) using a ATI Mobility Radeon 9700, and ported to MS Visual Studio 2005 on a Windows XP machine. This move was made to fulfill the assignment requirements.
Project Features and Design Choices
The following is a set by step discussion of the implementation of this project. Many elements in the main code borrow from previous projects, particularly the GLUT functions such as myGlutMouse and myGlutMotion. Their use is fairly self-explanatory.
Upon launch, the OpenGL state is initialized, and using SGI's *.rgb read routine, several textures are bound to texture objects and associated with texture units. Two display lists are created, one that draws a filled gluCylinder of length 10.0 and radius 1.0, and one that draws a wireframe cylinder of the same size. Two additional display lists are created that store different material properties for the ground and the snake. A dim ambient light is created to provide a limited work light.
The shader components are then linked and compiled into the program. This is done using code adapted from the provided glslExample.cpp. The shader objects are created, and the source code for each are associated with the correct object. The shader objects are compiled using glCompileShaderARB, and then both the vertex and fragment object for each shader program are linked to the GLSL program, and the program itself is compiled. This process is completed for the vertexGroundBump and fragmentGroundBump objects as the glslGroundBump shader program, and for vertexWiggle and fragmentWiggle for the glslWiggle program.
The uniform variables that reference texture units are initialized, and each of the four necessary textures (color and normal map for the ground and the snake) are passed here. At this point it is necessary to mention that the snake normal map texture originated as a height map texture. This texture was converted to a normal map using Normal Map Generator (NMG), a program created by Marabese Nilo to create normal maps from other images. The resulting normal map is scaled in NMG, and saved at a *rgb image. This method was determined to be quicker than using NVIDIA's bumpmap_to_normalmap.cpp routine and rebuilding the normal map each time the program is launched.
As the main display loop is entered the project matrix and modelview matrix are initialized, and a directional light source is created. The ground is drawn using GL_QUADS with the appropriate texture coordinates. This quad is drawn using the glslGroundBump shader program, which will be detailed later.
The snake cylinder is positioned and drawn using the proper display list calls. Texture coordinates with the gluCylinder are generated automatically with the call gluQuadricTexture(cylinder, GL_TRUE); At this point the snake control uniform variables that will be passed into the activated glslWiggle shader program (described below) and located and initialized. Finally, the buffers are swapped.
The Ground Shader
The glslGroundBump shader program operates in object space to texture and bump map the ground quad. The vertex component of this shader is very simple, it simply passes the position of each vertex to be interpolated by the fragment shader, and positions each vertex according to its fixed functionality position in the modelview projection matrix. Each texture coordinate interpolate is bound to the gl_multiTexCoord0 value of the vertex.
The fragment component of this shader bump maps and colors the fragments for output. Using two sampler2D variables that reference the color and normal textures, each fragment's normal is perturbed according to the normalMap sampler, and colored according to the colorMap sampler. A simple per fragment diffuse lighting is computed using the diffuse components and ambient components of each fragment, and then is sent to the next rendering process. Each fragment's color is generated according to the following equation, using diffuse lighting:
gl_FragColor = colorMap * material.diffuse * light.diffuse * (normalMap <dot> light.direction) + ambient + globalAmbient
The Snake Shader
The glslWiggle shader program demonstrates the power and variety that OpenGL shaders can deliver, as it transforms a simple, open-ended gluCylinder into a wiggling bump mapped snake. The cylinder has a length of 10.0 and a radius of 1.0. Using a quadratic function, the radius of the cylinder is tapered to a smooth point from 0.0 - 1.0 and from 9.0 - 1.0. The cylinder between 1.0 and 9.0 is scaled to a radius of 0.33. This produces a tapered, slim cylinder. The cylinder verticies are displaced according to a sine function. A TBN tangent space matrix is computed for every vertex, and the tangent space light vector (the negation of the light vector) and the tangent space eye vector are passed to the fragment program. Additionally, a texCoord varying variable is created from the texture unit at each vertex, and these coordinates are scaled along the t direction to make the snake color and bump texture more realistic (i.e. rounder, smaller bumps.)
The fragment shader perturbs the normal according to a sampled normal map (the normal map, as mentioned above, is converted from a height map using a third party program), and scaled and biased, and normalized accordingly. The diffuse, ambient, and global ambient terms are computed using the same general formula as above, but in tangent space per vertex than in object space. Finally, using the Blinn/Phong half angle method of computing specular lighting, the specular component of the fragment is calculated, and the gl_FragColor is sent down the pipeline. The half-angle method of lighting is detailed below:
gl_FragColor = colorMap * material.diffuse * light.diffuse * (normalMap <dot> tangentSpace.lightDir) + ambient + globalAmbient + (material.specular + light.specular + (normalMap <dot> (tangentSpace.lightDir + tangenSpace.eyeDir))^material.shininess
Graphical User Interface
The GUI implemented in GLUI provides additional functionality. The light position and intensity can be controlled under the "Light Control" rollout widgets. The snake can be drawn as a filled cylinder, or as a wireframe model using the associated radio button. The speed of the snake movement can be controlled, as can the number of wiggles, where 1.0 wiggles is a single curve, and 2.0 wiggles is a complete sine function S. Additionally, the arrow keys move the snake across the ground surface. when using the arrow keys, the user must ensure that the mouse cursor is in the viewport, as the behavior with the arrow keys when the cursor is over the GUI or the desktop is undefined.
Known Bugs and Issues
- The wiggle vertex shader transforms each vertex according to a sine function through the center of the cylinder. The snake "ribs" are thus transformed along one axis, which slightly squeezes and expands the snake radius as it wiggles. This is more evident the more wiggles the snake has ( greater than 3.0).
- The specular component for the wiggle.frag Blinn/Phong half angle specular lighting may not be correct. The behavior visually does display a specular component to each bump image, but it is difficult to diagnose whether this specular is correct. The assignment specifications mentions that the power function is not available on the fragment shaders in the windows lab, although in my experience this function worked and produced no errors. Different behavior was displayed on the OS X workstation in terms of the specular visual display, which made it more difficult to troubleshoot.