The Octagon Engine

Technical Information

"The Octagon" is a game engine demonstrating per-pixel lighting, bump mapping, shadow volumes and particles. It displays Quake 3 bsp maps, Quake 2 md2 and Quake 3 md3 models.

Everything visible is rendered with its decal texture multiplied by a small constant plus its emissive texture, in order to provide the base for adding light. Then, for each light, stencil shadows are cast for each object which the light influences, and then a hardware-specific codepath lights the surface. After all lights have been considered, particles are added.

Maps

The maps used are Quake 3 .bsp maps. These were chosen because of their support for curved surfaces (patches), which are rendered along with the polygon faces. I chose not to render the meshes from the map file, as I would have to do extra work to light them and make them cast shadows. This would have brought no benefits because the map objects can easily be loaded in as static models.

Models

The program also supports animated .md2 Quake 2 models and static Quake 3 .md3 models. These models are loaded into a standard model class which handles drawing, animation and so on. Quake 3 models are not animated as they require the use of the "tags". These "tags" are not required by any other model format, so I did not want to add them to my general model format. I chose to implement the Quake 3 models so I could use the torches and other map objects which are available in that format.

Visibility Testing

Gross visibility testing is performed using the bsp and frustum culling per sector. This is then refined by testing if each individual face is within the camera frustum. The same procedure is used to see which faces are lit by each light.

Lighting

There are two classes of light available for use. Firstly, there are standard point lights. Secondly, lights with projected cube maps can be used to emulate spotlights and anisotropic light sources.

Lights are tagged as visible if their bounding sphere lies within the camera's frustum. The more lights that are visible, the more passes are required. The number of lights visible thus has a huge effect on the speed of the scene.

Firstly, everything visible is rendered with its decal texture multiplied by a small constant (0.3) plus its emissive texture, in order to provide the base for adding light. This pass also serves to lay down the data in the z buffer for the passes ahead, which use more expensive fragment shaders. Then, for each light, stencil shadows are cast for each object which the light influences, and then the hardware-specific codepath lights the surface.

Codepaths

The engine currently supports three different codepaths, which are used to take advantage of the capabilities of the hardware on which the engine is running. Only the lighting calculations are dependent on the codepath used. The original ambient pass, the shadow code and the particle code are all the same no matter which codepath is used.

There is a standard codepath, using standard OpenGL 1.3 features. This draws everything in three passes. The first lays down per-pixel attenuation. The second modulates this with a bump map, and the third colors the result using the decal texture, any projected cube map and the light's color.

The NV20 codepath uses ARB_vertex_program, NV_texture_shader, NV_register_combiners and EXT_blend_color to reduce the number of passes required on Geforce3 and above hardware. It also introdces specular highlights. This codepath draws objects in one, two or three passes depending on surface and light parameters. If the light does not have a projected cube map, the diffuse equation can be evaluated in a single pass, whereas if the light has a cube map, two passes are required. The specular highlights are then added to the shiny surfaces in a further pass.

The R200 codepath uses ARB_vertex_program and ATI_fragment_shader, along with the 6 texture units available on Radeon 8500 and above cards from ATi to evaluate the entire lighting equation (diffuse and specular) in a single pass.

Particles

After all of the lights have been handled, particles which emit light of their own are added to the framebuffer. Then, we loop through the lights again and draw any non-light-emitting particles (such as steam particles), lit by any lights which influence them. If a light which is illuminating a particle system has a projected cube map, this is also taken into account, as can be seen by the stained glass window in the demo level.

Texture Maps

The geometry uses up to three texture maps for each surface. As well as the standard color "decal" map, each surface has an emissive map, showing how the surface is "self-lit", and a normal map for the bump mapping. In the alpha channel of the normal map, the gloss map is stored. This is used to modulate the specular lighting to produce surfaces which are shiny/not shiny on a per pixel basis. The default for an alpha channel is white, but we want surfaces to default to being non-shiny. So, gloss is stored inverted. This is handled when a gloss map is loaded by inverting the map. The map is then inverted again as part of the lighting equation.

Console

The console can be activated by pressing the F2 key. This allows you to change the codepath currently being used and to control various rendering parameters. For a list of console variables, see "data/cfg/cfg.txt". Groups of console variables are stored in "*.cfg" files. On first startup, the console loads "default.cfg". When the console is shut down, it saves the current configuration into a file called "previous.cfg". This is then loaded on subsequent executions, in order to preserve the previous settings.

Collision Detection

Simple bounding sphere collision detection is implemented between the camera and the bsp polygon faces.

Code Organisation

All drawing is controlled by a central render manager. The map and entities submit to the render manager what will be drawn, and the render manager handles the actual drawing code. Thus, triangles are easily sorted by texture, and features such as NV_vertex_array_range and ATI_vertex_array_object can easily be implemented in a single module. All geometry is stored using a single vertex format so that triangles can easily be batched. The vertex format contains the vertex position, the texture coordinates and the tangent space basis for that vertex.

Models, particle systems and lights are grouped into entities. Up to one of each of these is part of each entity. The entities follow paths which are loaded in from files in "data/paths". The lights are stored in "data/lights", and the particle system scripts in "data/particles". In "data/maps" the list of entities which populate the map can be found.