CS 6610 Advanced Computer Graphics - Project 2

Zach Gildersleeve
October 13, 2006

CADE login: gildersl
Graduate level credit

robot image

Program Description

This program creates a room with several objects, each demonstrating a different aspect of OpenGL texture mapping and associated functions. The room floor is texture mapped with a multitexture light map, which is controllable via the GLUI GUI. The cone in the center of the room demonstrates different texture environment functions, such as blend and replace. The sphere balanced on the cone is mapped with a dynamically updating cube map. Above the sphere, a dragonfly model uses several texturing methods as well as physical manipulation of the texture matrix to attempt at realistic texturing. Other features are available via the GUI, and will be discussed below.

Source Code

The souce code, both the Xcode files and the Visual Studio files, can be found here (25.60MB .zip).

Development Platform

This project was coded in C++ in Xcode on OS X (10.4.7) and ported to MS Visual Studio 2005 on a Windows XP machine. This move was made to fulfill the assignment requirements. In Visual Studio it was necessary to include GLEW to implement the cube map, multitexturing, and texture units.

Project Features and Design Choices

What follows is an in-depth description of the main program, and details of why certain design choices were made.

image

Upon building the GLUT window, the Init() function initializes the OpenGL state, and loads the texture images into texture objects. The .rgb images are opened with the provided texture.cpp code, and then bound into texture objects. Likewise, an OpenGL implementation of a cube map is created - GL_TEXTURE_CUBE_MAP_EXT - and NULL placeholder images are loaded into the cube map. Finally, Init() creates two texture units, which will then be used to multitexture the floor. This step is only necessary for textures that will be multitextured, otherwise the default texture unit of GL_TEXTURE0 can be used to simply bind texture objects. The floor plank texture object has mipmapping enabled, and mipmaps are built with gluBuild2DMipmaps(...), although mipmapping here is visually unnecessary for the small geometry in the program.

As the main display loop is entered, a light is created that stays attached to the camera position, according to the assignment requirements. The camera itself has a conditional operator that clips the geometry so that the room can always be seen into. The room is drawn, along with the cone and dragonfly. The sphere is drawn separately, as all the room geometry must be drawn first if it is to be reflected correctly in the sphere. The floor is drawn first, using multitexturing, and a texture environment mode of GL_MODULATE on the light map image produces the correct illumination spot by attenuating the underlying image. Functionality is added that binds the light map bright spot to the geometry of the floor, which was done to prevent the light map from being moved "under" the walls and thus off the floor. The lightmap is placed using glMultiTexCoords(), which allows easy feedback from the GLUI 2D translation widget. In the instance of the floor, and with all geometry, a separate switch function establishes the material state depending on the geometry being drawn. This will play more of a roll when adding specular highlights to the sphere, and changing colors of geometry, etc. Finally, with the floor, calls to glMultiTexCoord2f() position the multitextures on the floor. Positioning the light map is possible with a GLUI callback, discussed below.

Multitexturing is disabled, and the rest of the room is drawn. The walls and ceiling can be drawn with or without textures, and the texture environment can be either GL_REPLACE or GL_MODULATE. GL_REPLACE on the room essentially disables the lighting, and would be the choice to bake lighting on the geometry. GL_MODULATE of course does use the lighting.

The cone is built from a single triangle fan. Appropriate normals are calculated with functions provided in vector.cpp, which is a growing library of vector, matrix, and 3D math functions. Lighting again is disabled for the cone, which showcases the texture environment modes that can be selected for the earth image. When using GL_BLEND, the blend color is set to red. The cone can be rotated around its Y axis by pressing the 'r' key.

dragonfly image

The dragonfly is drawn via several functions that draw a sphere, a wing, or a leg. The sphere function, CreateSphere(...) attempts to add control of texture coordinates to a sphere object, rather than the default 0.0 - 1.0 that is provided with glutSolidSphere(). While it would be possible to draw each sphere that makes up the dragonfly using glutSolidSphere(), each sphere would need its own separate texture image, or the texture object would need to be manipulated through calls to change the texture matrix and glTexSubImage2D() etc., and this would make it very difficult to control the texture placement. Rewriting a sphere routine to one that passes in (s, t) coordinates attempts to provide more detailed control. As such, this method is not ideal, but as the dragonfly is above and beyond the scope of the assignment, the CreateSphere() function was deemed sufficient. The most intuitive control of the texture placement requires limited manipulation of the texture matrix, but textures the dragonfly body with only one texture. It would be possible to add the wing texture image to the body texture image, to fully texture the dragonfly with only one image.

The dragonfly eyes are mapped with the cube map generated for the reflective sphere (this will be discussed later); it was deemed unnecessary to re-render the cube map just for the eyes, so the dragonfly is fully reflected in its own eyes, as would be expected.

The reflecting sphere itself is drawn and mapped with the cube map to simulate the reflections. Initially, the cube map contains the temp images, and is updated after the first buffer swap. The sphere is drawn with glutSolidSphere, as the 0.0 - 1.0 texture coordinate system this function provides is exactly what we want for the reflections. The sphere function also must manipulate the texture matrix. Here, it is necessary to rotate the texture matrix according to the camera's position relative to the sphere, which will update the reflections accordingly. Without these rotations, the cube map reflection will stay static on the sphere, and the reflection illusion will become apparent as soon as the camera moves from its starting position. First, the texture matrix is flipped 180.0 degrees, and then rotated by the arccos of the dot product of the camera position and look at position as such: angle = 57.3 * acos(v_dotProduct(eyeVector, view); and rotates around the axis of the cross product of these two vectors. Using a light model of GL_SEPARATE_SPECULAR_COLOR adds a specular highlight to the sphere after it is mapped with the cube map, which adds to the reflective illusion of the sphere.

sphere image

At this point, the rendering buffers are swapped, and then the routine to render the cube map is executed. The buffer swap hides the rendering of the cube map faces, and by incorporating the cube map routine in the GLUT display function, which is then looped during glutMainLoop(), this updates the cube map during each buffer swap, making the cube map images fully dynamic. It is not necessary to manually update the cube map, and this function is not provided in the GUI.

The makeCubeMap() function rotates the a camera with a 90.0 degree field of view around the OpenGL cube map, defined as +X, -X, +Y, -Y, +Z, -Z, encapsulated in a two dimensional array of positions. The appropriate cube map texture object is bound, and glCopyTexSubImage2D(...) replaces the image there with the new render. Each cube face image must be drawn out from under the GLUI, which will mask the image unless the move is made. The cube map is positioned at the origin, which is where the reflecting sphere is drawn. It would be simple to change this position, and have other points be the center of the cube map, but unnecessary to the purposes of this assignment. At the conclusion of the cube map function, the camera and viewport is returned to where the user had positioned it previously.

Graphical User Interface

glui implementation

A GUI implemented in GLUI provides additional features. The Quit button exits the program, and the ResetCamera button resets the camera to its default position, at (0.0, 0.0, -40.0) looking at the origin.

In the Light Control, the light map position can be controlled by the GLUI position widget, and reset using the button. The light map is constrained to stay on the floor geometry, and cannot move "under" the walls. Because the cub map is updated dynamically, the reflection of the light map can be seen in the sphere as the light map moves. The light intensity, which defaults at 1.0, controls the intensity of the light fixed above, behind, and to the right of the camera. The room environment mode also plays into how the light intensity appears; when the room is mapped with GL_REPLACE, no lighting is used on the walls, and the light change is only visible on the dragonfly's eyes. If the lighting was fully mapped with lightmaps, as we might expect for games, no OpenGL lighting would be necessary.

The cone texture function is controlled by its associated radio button group, with the GL_BLEND mode using a red color. The user can also select if the entire room is textured, or just the floor, as the entire room textured with GL_REPLACE can be claustrophobic. Finally, the room texture environment can be selected between modulate and replace, and discussed previously. The user cannot manually update the cube map texture, as this is done dynamically.

Known Bugs and Issues

The texture matrix rotation in the reflective sphere function flips direction when the dot product equals -1.0, which causes the cube map to quickly rotate around the z axis. This only happens as camera looks straight down the Z axis towards -Z, or when looking straight down the Y axis towards -Y. Additionally, when looking down the Y axis towards -Y, the cube map will not rotate, but will stick on the current image, and the light map will wobble back and forward. This behavior vanishes when looking in the same direction, but at a slight angle, which gives a better visual image anyway.

The dragonfly wings have interesting behavior. The reflecting sphere, which is the only geometry that is drawn after the wings, is not visible when looking down through the wings at it. One solution would be to draw the wings absolutely last, but a more elegant solution would involve use of buffers – maybe next assignment. Merely turning the depth buffer off and back on again does not do the trick.