Thursday, June 12, 2008

The Beginnings of a Game

I recently started to put some serious work into a game, which I'll be calling "Biff! Bam!! Blammo?!!". The game is a glorified version of "Arkanoid", which, I'm aware, has a billion+ copies out there, but I choose this type of game mainly because the game logic is straight forward, which paves the way for more time spent on graphics and exploring interesting game mechanics.

These posts concerning the game are intended to record things that have driven me insane or insights I've gained while working on it. But first here are some pretty pictures:

This first picture is of the first 'world' - the game will be split up into worlds, each with its own theme - the theme of this first world being "Deco".



Next is a picture of the same scene but without the game pieces or HUD.



Finally here's a picture of the deco-block mesh that makes up the solid blocks in the Deco world.



Materials

So far I've developed 2 CgFx materials: a basic celshading/toon material and a basic phong material. Both materials have black outlines to remain consistent and to bring out more of the features of the various meshes.

I've designed materials so that they share all uniform and varying parameters - typically these are just various projection, view and world transform matrices, a diffuse sampler, specular colour, diffuse colour, shininess, and light positions/colours. My reasoning behind this is so that I can move all the parameters (in code), up into a superclass for materials. This allows me to abstract any of the game materials, where I may change the parameters of a material without needing to know the material I'm dealing with. Of course, I lose this abstraction if I ever create really complicated materials with unusual parameters - but for the purposes of this game, I doubt I'll have any materials of that sort.

To deal with the CGcontext and other persistent Cg-type objects I created a singleton manager for CgFx - this makes creating, getting, and destroying those objects easy and centralizes that functionality. It also provides a central place to load effects and check for errors in the Cg runtime.

Instancing Issues

Probably the most time consuming and troubling issue I've had so far was dealing with instancing of the blocks/bricks that make up game levels. When I first drew a full level with all of its bricks, CgFx materials included, I had a frame rate of 10... which seemed absolutely ridiculous on a NVIDIA 8800 GT - my goal for this game is to ALWAYS keep the frame rate at 60 fps. So at that point there was much science to be done; I had never dealt with extreme instancing before and I had some ideas of what the problem was...
  • Naive hypothesis: high polygon block meshes

  • Less Naive hypothesis, but still naive: using only one display list many times

  • Partly good hypothesis: overly complicated CgFx shaders (move outline pass out of shader, simplify some of the math)

  • Correct hypothesis: ALL material parameters are being changed for EVERY BLOCK (yikes!)
Obviously the first two hypothesis had little/nothing to do with the problem, the last two, together, had just about everything to do with it - but mostly it was the last. The issue when it comes to instancing is that every instance will have its own transformation, material properties, etc. but the mesh is the same... so in a perfect world why not just draw one mesh many, many times with changed parameters feeding into each respective material? Because it kills your GPU... one of the slowest parts of drawing a material is setting the parameters, especially the transforms - not only is that data being sent to the GPU but its also affecting the entire shader.

I was able to remedy the problem by rendering the entire level as a set of individual display lists for all the blocks, each display list with that block's full world transform set as vertex positions - sure it takes up more memory, but the blocks are really small anyway. Then, as I draw each of the display lists I only set the transform parameters once and the only varying parameter for now is the diffuse colour. This remedy put the frame rate back at 60 fps.

This is obviously NOT a solution that uses "instancing" in the sense of modern GPU instancing (i.e., one that uses the latest and greatest OpenGL extensions for instancing or even using pseudo instancing), but it's certainly a solution that works.