CS838 - Final Project

Modifying OpenGL Rendering on the Fly with Condor Bypass

or

How to Mess with OpenGL Programs without their Knowledge

by Jaime Frey

NOTE: There are no screenshots currently because this project runs on unix machines and I don't know have to screen capture on unix and I don't have ready access to an NT machine with an X server. I'll try to get some pictures up soon.

Introduction

For this project, I wrote a library that lets an external process to modify the rendering behavior of an OpenGL program on the fly. It works with any OpenGL program this is not statically linked to the OpenGL library and requires no modification or recompilation of the program. It does this by forcibly loading its own dynamic library into the program before the OpenGL libraries and intercepting certain OpenGL library calls. I do this with the help of Condor Bypass. The inserted library listens on a port for commands instructing it how to permute the OpenGL commands from the program. Currently, it can modify most lighting and material parameters and do simple cartoon shading and wire frame rendering.

Condor Bypass

Most form of unix support shared object libraries and allow the user to specify that some should be loaded before any others. This allows the careful library writer to intercept library calls meant for other libraries and do their own twisted things with them (which may include calling the original library function with modified parameters. However, how this is done can vary greatly from platform to platform.

"There's no problem in Computer Science that can't be solved by an added layer of indirection."

Bypass abstracts away the system-specific details of library call interception, allowing the user to write a single source file that will compile on many different architectures without lots of nasty #ifdefs and similar garbage.

Bypass was written by Doug Thain at the University of Wisconsin as a part of his Ph.D. work with the Condor project. Bypass can downloaded from the Bypass webpage.

Overal Structure

The basic structure of jefGL is pretty simple. It consists of a shared object library and controller program. The shared object loads itself into an OpenGL program and intercepts certain calls to the OpenGL libraries. Initially, it passes them through to OpenGL unmolested. It also listens on a port for commands from the controller program. When it receives a command, it starts modifying how calls are passed on to OpenGL, if they're passed on at all.

Currently, there are two basic forms of command the controller can send. First, it can tell the shared object to override the settings of certain OpenGL parameters. It can also tell the shared object to stop overriding these settings. Second, it can enable and disable special rendering effects, like cartoon shading.

Overriding Parameters

Overriding an OpenGL parameter involves two seperate commands: the command to enable overriding and a command to set the new value. Many new value commands may be sent to the library as the user changes the settings in the controller program.

If the OpenGL application tries to change an overridden parameter, jefGL doesn't not pass the call down to OpenGL. However, it can't just throw the new value away. It needs to keep it for two reasons. First, if overriding of the parameter is turned off later, jefGL needs to restore the latest value set by the application. Second, if the application later queries the value of the parameter, jefGL needs to return the last value set by the application, not the overriding value. This keeps the application completely unaware of what's going on.

Currently, most lighting and material parameters can be overridden. This includes the ambient, diffuse and specular settings of the lights and the global ambient setting. Individual lights and lighting as a whole can also be turned on and off. The ambient, diffuse, specular, and shininess settings of objects can also be modified, as can the color parameter (when lighting is disabled). Changing material and color properties modifies all objects in the scene.

Other Effects

Wire Framing

Two forms of wire framing are supported. Wire framing by itself and wire framing over rendered objects. If wire framing by itself is enabled, depth testing is turned off so that all wires are seen. Both forms of wire framing are done the same way. In glBegin, an array of vertices is started. On each call to glVertex*, the new vertex is appended to the list. In glEnd, the vertices are replayed inside a new begin/end pair with polygon mode set to GL_LINE. If wire frame only is selected, the vertices are collectd in glVertex*, but the calls aren't passed on to OpenGL (so the rendered version isn't drawn). If wire frames with render objects is selected, polygon offset is used to ensure that the wire frames show up on top of the rendered surfaces.

Cartoon Shading

To get a cartoon shading effect, you want to flat-shade each polygon in either a highlight or shadow color. The highlight color is used for polygons facing the light source while the shadow color is used for polygons facing away from the light source. This can be calculated fairly efficiently by dotting the normal of each vertex with a vector from the light to the vertex. The sign of the dot product tells you which way it's facing.

In jefGL, glNormal calls are caught and the normal saved (after having the modelview matrix applied). Then, on each glVertex call, the normal is dotted with a vector from the light to the vertex. If the result is negative, the normal is facing towards the light. Otherwise, it's facing away. In cartoon shading, lighting is disabled and glColor is set to the highlight color for light-facing vertices and the shadow color for non-light-facing vertices. The highlight and shadow colors are calculated from the material and light's ambient and diffuse settings in the same way OpenGL calculates lighting colors (the shadow color has no diffuse component while the highlight has a full diffuse component).

Limitations

There are limitations to the interesting things you can do when all you see are the raw OpenGL calls. Since you don't know the context in which they're made, you can't reliably implement effects that would be easy to do from the application level.

One such effect is the outlining of cartoon shaded objects. The effecient algorithms I found required knowing which sets of polygons form a single object or the drawing of all front-facing polygons of an object before the back-facing ones. Neither of these is easy to know at the level at whcih jefGL sits.

Another hard effect is the setting of a different viewing position. Setting a new viewing position thats relative to the one specified by the application is easy: just prepend your transformation matrix to the front of the modelview matrix. But setting an absolute new position is difficult. This is because it's impossible to reliably differentiate viewpoint-setting transformations from object-positioning transformations (unless the application is only using gluLookAt to set the viewing position).

Another difficult task is setting properties for a limited set of objects. As mentioned earlier, it's hard to reliably distinguish one object's polygons from the next's. So when jefGL changes object properties, they affect everything in the scene.

There is a performance penalty for using jefGL. Applications can be slowed down by up to 25% even when jefGL isn't modifying anything.. This is primarily due to the interception of all glVertex* and glNormal* calls, which is done for the cartoon shading and wire framing. Unfortunately, there isn't a way to disable the interception of selected calls at run time with Bypass. Otherwise, you could decide to not intercept glVertex* and glNormal* calls when cartoon shading and wire framing are turned off.

Files

Binaries for Intel Solaris Binaries for Sparc Solaris Source files

Return to Jaime's Class Page