"Brothers" by Chris Klochek

All lighting is per-pixel lighting, with both specular and diffuse components. Per-pixel lighting implies that rather than evaluating the traditional lighting equation at the vertices, the equation is instead evaluated at each pixel (more or less) along the surface of a given polygon. The normal information for this kind of lighting comes from a texture map which contains, encoded as RGB colors, the XYZ components of a set of normals. These kinds of texture maps are, for obvious reasons, referred to as normal maps. Note that the specular component is not actually modulated in any why by any coefficients--it's faster/easier, and stands out more, especially in a compressed video. The lighting is done in tangent space, with vertex programs doing the necessary transforms for lighting vectors into tangent space, after which the fragment shader will perform the necessary dot-products on the normal maps to produce the correctly-shaded result. Cube maps are used for per-pixel normalization of the light-vectors.

The basic shading algorithm for all objects is:

do depth pass in black

for each light

add that light's contribution the scene

modulate the result by the diffuse texture

None of this would be working in real-time without some serious visibility determination going on. Luckily, the Doom3 datasets provide some nifty ways to speed things up. Firstly, the environments in Doom3 are divided (by the artists) into areas, which are then linked together using portals. A BSP-tree is used to first determine the location of the eye. Portal culling is then used to recursively cull away large sections (areas) of the environment that are totally hidden. Sub-objects that are within the actual areas are then culled via frustum rejection. Doing this will result in a great speed-up, but note that because the number of times a given object is drawn is proportional to the number of lights affecting it, we must employ yet more culling techniques, this time to determine exactly the number of lights affecting a given sub-object within an area. Lights in Doom3 have not only a position, but a radius, defined by an axis-aligned bounding box. Using this bounding box, along with the bounding boxes of the sub-objects within an area, we can determine, before even running the program, whether a given light will affect a particular sub-object. Finally, note that if the eye cannot see the entire bounding box of a given light, than that light cannot possibly contribute any light to the given scene, and hence can be ignored as well. All these optimizations, when taken together, can allow the scene to be rendered at highly interactive rates.

For dynamic objects (such as models), the task is made slower because we cannot know exactly where an object is going to be, and therefore which lights are going to affect it. Therefore, the next best thing to do is to simply find the closest lights to the model at run time in the area the model currently occupies, and add their contribution to the model's lighting. In this particular implementation, a limit of 5 contributing lights was placed on the models, in order to ensure decent performance and attractive results.

Models, in Doom3, are specified by a heirarchy of bones (a skeleton), and a set of vertices. For each vertex, a set of bones that that vertex is attached to, as well as the weighting by which that bone affects that vertex, is present. Animation data is given by a set of position and angle attributes for each bone, when then change over time. These bones then move the vertices, producing an animation.

Collision detection in Doom3 is handled by an underlying "collision map", which is an artist-defined set of polygons against which an entity can collide. The large number of polygons as given in a Doom3 map would be too large to handle with any conventional collision detection system. Moreover, it would be quite unneccessary, as most people would not be able to tell if an entity collided with a high-poly cylinder, or a tight-fighting quadrilateral that wraps that cylinder. Thus, a low-poly collision map encompasses the high-polygon visual map. However, "low-polygon" is still a relative term here, and an efficient culling mechanism must be put in place in order to ensure good performance from the collision system. I was unable to ascertain exactly what node-structure Doom3 uses for it's collision culling, so I used an Octree structure I wrote, which gave acceptable performance for the collision system. The actual collision detection algorithm used is the separating axis theorem for oriented bounding boxes and triangles. This algorithm proved quite simple and quick to implement. Note, however, that none of the model bounding-boxes ever rotate, so they actually remain axis-aligned throughout the entire animation.