The technique of using 2D imposters is becoming more and more popular in the world of computer games. The aim is, without loss of detail, to reduce the geometric complexity of a 3D scene by caching portions of the scene as images. 3D objects in the scene are captured into separate images. When the final scene is generated the cached images, or imposters as they are known, are rendered in place of the 3D objects. In this way an imposter is a simplified representation of a complex 3D object. As modern games grow larger and demand more detail than ever before developers are increasingly looking for new and innovative level-of-detail algorithms. The common place practice of using a progressively lower detail series of hand-authored meshes is not necessarily enough to meet the demands of modern games and more advanced solutions such as 2D imposters are required. As the topic of imposters is gaining acceptance and importance in the industry, numerous good articles and chapters have already been written, however I have not yet seen an article that covers the implementation of an imposter system. This article provides some basic theory, but is mostly concerned with, as the title suggests a simple and efficient system for dynamically generating and rendering 2D imposters.
Imposters can be used in many situations, however they are very well suited for complex and detailed games that have a large amount of distant static background geometry. In these games the users' attention is likely to be in the foreground and they will not notice the imposters in the background. Less time spent processing and rendering geometry leaves the game more time for the things that really make the game: logic and AI.
Imposter generation falls into two categories. Static or offline imposters are those that are generated offline, usually by an artist. Statically-generated imposters have been in use for many years and are more commonly known as sprites, particles or billboards. The classic example in this case is the often seen "billboard tree" constructed from billboards at right-angles. This tree is an example of an imposter that has four viewpoints. This article is about dynamically-generated imposters. They are similar in concept to static imposters and are still implemented as billboards; however they are regenerated at runtime by rendering an image of a 3D object to a texture.
|Figure 1. Screenshots from Tycoon City: New York. A modern PC game that is due for release in early 2006 and simulates has a detailed city environment. Far-left has no imposters. Middle has barely noticeable imposters in the background. Far-right shows imposter outlines.|
This article is aimed at developers who want a starting point for creating their own imposter system. The code presented in the article is intended to be simple and adaptable to different environments and needs. Development of an imposter system is complicated by numerous issues. It is essentially an R&D task and requires experimentation to solve the mathematical, visual and performance aspects that come with the territory. To read this article I assume that you are experienced with the C/C++ programming language and have an understanding of 3D graphics concepts. You should at least be familiar with the DirectX 9 API and you might want to have the SDK help to hand.
A 2D imposter is a simplification of a complex 3D object, implemented as a billboard that is textured with a rendered image of the 3D object. The purpose of using imposters is to reduce the time required to render a 3D scene. It works by caching images of 3D objects and using these images in place of the real objects. Using imposters to decrease the amount of work performed each frame results in less time spent rendering. In DirectX this equates to a reduction in polygon and texture upload, draw calls, state changes, and more importantly, less CPU processing of geometry.
|Figure 2. An imposter generated from a truck. This shows the 3D object behind its 2D imposter. The green lines converge on the camera position that was used to generate the imposter.|
Generation of an imposter consists of the following steps:
Imposter billboard vertices are derived from the bounding box of the 3D object. The billboard is such that in screen-space, from the current viewpoint, it fully encompasses the 3D object. The billboard and the current camera position are used to compute the necessary view and projection matrices. The final stage in generating an imposter is to initialize render-state and render the 3D object into the texture. This texture is called the imposter texture throughout this article. The imposter texture must contain an alpha channel. The alpha represents where in the texture the imposter is: opaque (alpha = 1.0) where the imposter is; and transparent (alpha = 0.0) where the imposter is not.
|Figure 3. Image of four 3D objects rendered into a texture. The cyan area of the texture is transparent (alpha = 0.0).|
Once the imposter is cached it is used for multiple frames of rendering. Imposters are rendered into the final 3D scene using alpha testing or alpha blending and are placed at the position of the 3D object they are replacing. The key to imposters bringing a performance gain is to reuse cached imposters over as many frames as possible. To this end imposters should only be regenerated when it becomes necessary to maintain the illusion of a cohesive 3D scene. There are certain conditions that will make the imposter illusion become apparent. There are global conditions such as camera viewing angle and light color and direction. There are local conditions such as animation, position, and orientation of the 3D object. When these conditions become so extreme that imposters become noticeably two-dimensional, they must be regenerated. The variation between the appearance of a 2D imposter and the 3D object it represents is what I call "imposter error."
Visual Quality Concerns
For imposters to be useful they need to not only be more efficient than rendering the 3D object, they also need to look as good, or (if a small sacrifice in quality is acceptable) almost as good as the real thing. With practice and experimentation most of the visual quality problems are solvable.
|Figure 4. Side by side screenshots of a 3D object next to its imposter. Note that at a distance the two are almost indistinguishable.|
The biggest visual problems are:
The common DirectX alpha blending mode “source alpha-inverse source alpha” cannot be used in models that are going to be impostered. This is easily solved by using low-detail non-alpha versions of models when rendering them as imposters. Note that alpha-testing can be used with imposters to implement transparency. Pre-multiplied alpha can also be used as discussed in Game Programming Gems 2.
Visual popping is almost unnoticeable with good use of time-based alpha blending. You can even alpha-fade between imposter viewing angles so that imposter regeneration is barely noticeable, however that is not implemented in this article as it requires a larger render-texture budget. It should be noted that when alpha blending is used, imposters need to be depth-sorted and rendered back-to-front for the transparencies to work correctly. If alpha-fading is not required, it is possible to use alpha-testing instead which makes the depth sort unnecessary. There are many high-performance sort algorithms available.
Selecting the right texture resolution is important. A texture resolution that is too low will mean that imposters look pixelated. Additionally if the camera gets too close to an imposter or the imposter is too large then a low resolution texture becomes very obvious. For this article I have empirically selected the texture resolution of 64-by-64 for each imposter. The problem with hard coding a texture resolution is that it doesn't work very well with different sized objects or object distance. For example, larger and closer imposters will require a higher resolution than smaller and more distant imposters. For simplicity in this article, I will stick with the hard-code resolution; however the section Taking Imposters Further discusses extensions to the imposter system to efficiently handle run-time selection of different texture resolutions.
When imposter error becomes too extreme, imposters need to be regenerated before the problem becomes noticeable by the user. The conditions that cause imposter error will have more or less impact depending on the type of game, but typically they are based on camera viewing angle changes, object animation, movement and rotation, and lighting changes.
In this article the conditions are dealt with in the following ways:
Note that changes to object orientation are not tested for in the sample code. However, it is a simple condition to support. The test of the angle between camera vectors needs to be performed in model-space rather than world-space so that it will account for changes in object orientation.
It should be noted at this point that use of hardware fogging can help hide the visual problems associated with imposters. Fog is your friend, just remember that you only need to fog the imposter billboard and not the 3D object, otherwise you may end up with double fog!