In Praxis… Impostors

It’s been a while since our last blog post, but it’s only because we’ve been hard at work, we swear! We haven’t even thought about rushing off to the Bahamas with your hard-earned money!

This time I’m going to shed a little light on a little something that makes it possible to render massive and densely populated worlds in real time: impostors.

To put it simply, an impostor is a flat polygon that looks like a more complex piece of geometry. They are often used as the last step in a set of levels of detail for an object. To make them look as convincing as possible, the textures applied to impostor polygons are usually rendered by the 3D engine rather than being created by artists. Reset has a deferred renderer so this is particularly simple: we just render a mesh into a tiny G-buffer and then use a special shader to splat copies of it onto the main G-buffer.

Sometimes impostor textures are updated dynamically based on the exact point of view. This is a fairly complicated system for a small team to develop, so we just render a fixed number of viewing angles for each mesh as a preprocessing step. At runtime all the data is available statically which makes rendering the impostors into the scene extremely simple and efficient. Transitions between meshes and impostors (as well as between meshes of different levels of detail) are handled with dithered cross-fading so there is no popping.

The island has about 100000 trees in total. In this particular view you can see a bunch of high detail tree meshes in the foreground as well as about 38000 tree impostors in the distance.

As development progresses the island will get populated by an increasing number of other objects and we will be able to use this system for them as well.

Would it be feasible to keep an impostor for each tree from the sun’s P.o.v. and splat ‘em into the shadow buffer also? I get that you’d pay a multiple of the fill cost unless you can cull well for each shadow LOD. But it would have helped the visual quality quite a bit, assuming it wouldn’t just reveal the shadow buffer(s)’s resolution(s).

Do you guys copy to shader-resource-only texture2D’s for the impostors, or do you keep them in the rendered SRV+RT versions? I am curious which way helps with texture cache coherency the most. The access pattern when you copy them out to the real RT is very top-down, so maybe SRV+RT is more favorable.

Are the artifacts in the clouds due more to the higher-res for a screenshot or to the elevated camera position? I really admire that you have tuned the cloud algorithm so closely to the target application (in terms of your game’s limits on resolution and camera position). That takes real dedication and skill! Reminds me of Doom when it was ported to Windows and had to run at 640×480: When the p.o.v. dropped down to the floor (i.e., when you die, which I did a lot), you’d get these wobbly pixel artifacts. Made me realize how tightly all of Doom’s pipeline was tuned for 320×240.

I answered my own question and didn’t realize it: The impostors already cast shadows: the buildings are clearly impostors due to their lower resolution, and they cast obvious shadows. The only reason the distant trees don’t cast crisp shadows from their trunks is that the shadow buffer resolution is too low. So this shot shows three examples of you guys expertly tuning the algorithm very tightly to the target resolution. In the real game, at the actual target resolution, neither the impostor resolution nor the shadow buffer resolution, nor the cloud artifacts will be noticeable. Well played!

Actually, “billboard” merely means a view-aligned quad (for some choice of “view-aligned”). “Impostor” implies a lot more: a cached rendering of the object with periodic re-renderings as some metric of visual quality is exceeded. The impostor objects don’t even have to be quads. Tom Forsyth, who coined the term “impostor”, used cubes as his impostor objects in Startopia. And in his original paper on the subject he clearly calls out “billboard” as a mere implementation choice for an impostor.