For this issue of the ever-continuing “Performance Art” series, I’ll talk a little about flat-shaded or colored polygons and the use (or overuse) of bones in a model.
Always use a texture:
In the past, when the memory on graphics cards was very small, we tried to save texture space by flat-shading or assigning a single color to groups of polygons. This provided color information to parts of a model that didn’t need the detail of a texture and saved memory in the process. However, with the advent of pixel shaders and graphics cards with a gig of memory on them, this is actually a performance killer now. The reason for this is simple, every unique color is a new constant switch. And every constant switch is a new draw call. So those 100 flat shaded objects in our models that we thought would save texture space are now actually causing 100 new draw calls. Sure, they’re cheap draw calls, but they are draw calls none the less. So how do we fix this problem? Simple. Save a small space in the texture sheet to have 4×4 blocks of solid color. Anything that needs to reference that color, simply take all the UVs for those parts and shrink them way down to fit in the center of that 4×4 block (with room on the edges to compensate for mips). Voila, it’s still a single draw call and you get the benefit of saving texture space since you’re only using the equivalent of a 64×64 texture sheet for a full 256 colors. That can easily be fit in the gaps of a standard 1024×1024 texture sheet.
Bones Ain’t Free (especially not ones in a big ‘ol hierarchy)
Bones are a wonderful thing. They allow us to do animation on the vertex level without having to store transforms for each vertex in a mesh (anyone want to try storing matrix transforms for 1000 vertices? Okay, I know, you can do it in a texture, but that’s out of scope for this discussion and still not free!) They also allow us to combine disparate animated parts in a model into a single draw call. However, there is a cost to having bones in a model. The hierarchy must be traversed recursively for each update for each frame. If you have a single node or possibly two in the hierarchy (like is the case for most of our autoskinning), then this is relatively cheap. Update parent node, update child node with parent’s transforms, update child node with child’s transforms, draw. 3 transforms and a draw. Well adding a single node to the chain drastically increases the complexity of the updates: Update parent node, update child1 node with parent’s transforms, update child2 node with parent’s transforms, update child1 with child1’s transforms, update child2 with child1’s transforms, update child2 with child2’s transforms, draw. It’s a geometric progression that goes from 3 updates for 2 bones to 6 for 3 to 12 for 4 to 24 for 5. And that’s every frame. I’ve seen some third party add-ons that have literally hundreds of bones, many with hierarchies 3 to 6 bones deep (and sometimes more). That’s great for saving draw calls, but not so great for performance overall. That would be the equivalent of making an aircraft that has every single Half-Life 2 character you might see onscreen at once as the wings. And that’s only one instance of that aircraft. Imagine multi-player…
Hopefully this helps. Keep your hierarchies simple and make sure to always have a texture. We let out a lot of rope with this version of Flight Simulator. As you watch us swinging from it, please listen to what we’ve got to say, that way only one of us has to hang.