December 06, 2016 | Filed under: production line | programming007
This is the short ‘story so far’ on animation compression for Production Line, my next game. I use my own engine that runs on directx9,a nd the game is isometyric in style, so uses a lot of spritesheet-style animation frames. In short, this is about how I enable animations like the one below (excuse the crappy gif compression, actual image is less pixelated in the real game), to fit into tiny amounts of video memory. (The disk version of that anim is 4MB, compressed for download its 355k). This is an ‘idle’ anim of a marketing manager in the game checking his phone to make all-important marketing related phonecalls…
You might think that cannot possibly be a 4MB animation, but you would be dead wrong. The actual source graphic is 128 wide by 256 high, after cropping, meaning that a frame of it gets delivered from the artist looking like this:
Thats no problem, but a complete animation cycle is 300 frames long, and I need two versions, one to look towards, one away (I can flip in X axis for the others). 128 x 256 x 300 is 9.8 million pixels, or a 4096 x 2,400 pixel sprite sheet, which takes up 39MB of VRAM. Assume both directions are in use = 80MB, 3 different characters is 240MB. Thats with one skin color and one gender. Ouch. So obviously we need to compress it.
I rolled my own compression system for fun, and to give me total control over how it works. The first step is to cut out any dead space and remove any duplicates. When our little dude is on the phone, the image doesn’t change, so we have a lot of duplication in that colossal theoretical sprite sheet. How did I approach this?
I decided upon a 64 chunk ‘grid’ for any image that would be animated, dividing it into 8 sections horizontally and vertically. With the raw image I get given, that gives me this:
Actually this is annoying because his hair only just clips into another row, which is inefficient… never mind. The most obvious thing here is that a lot of the animation frame is just empty space. In order to kill both the ’empty space’ and the ‘same as last frame’ problems in one go, I work out a CRC (basically a unique signature) for each of those grid squares, for each frame, and store it in a great big list of data as a pre-processing step.
With all that data in memory, I then go through each chunk of each frame, and look for previous frame chunks with the same CRC. If I don’t find any, I mark it as a ‘used’ chunk. if I do, I just make a note of which earlier chunk to use, and increment the use count on the old chunk. Once this is done, I can go through all the chunks and discard the ones that have a zero use count (or are blank).
Stage two is to create a whacking great spritesheet of all of the chunks that I actually *do* use, which looks like a weird mashup of imagery like this:
(It also has an alpha channel). I can then go through all of those ‘used’ chunks and make a note of the UV values for each of them in my new spritesheet. Now, the final stage is to go through every frame of the animation, and make a note of which chunks actually get drawn, and the index into my list of ‘used’ chunks. This means I now have a big text file for the animation that tells me which of the 64 maximum chunks of the spritesheet I need to draw in each frame. In the case of this phone-dude, that shrinks the actual texture memory usage by 95%, meaning I can easily have a bunch of different animations.
Thats where I am right now, and its pretty fluid, and works without bugs. The next step will be optimizing the code, rather than the source. An optimisation I managed today was to actually successfully only draw the non transparent chunks when I do the rendering of each frame, which reduces the number of polys and draw calls. The next obvious optimization is to spot when a chunks ‘source id’ is the same as the previous frame, and then not bother updating it at all. Right now, I redo the UV lookup and apply the values every frame for this dudes legs, even though they don’t move during the whole animation. Thats a bit too much processing for my liking.
I’m sure middleware *does* exist that does this, but I like to code stuff myself so I know 100% how it works, 100% how fast/slow it is, and can ensure compatibility with all the rest of my code and workflow. I’ve probably spent a week doing this, maybe a bit more, but I have a pretty cool system now that lets me knock up a 30 secon text file when I get a new bunch of animation source files, then hit the launch button and the game will do all the preprocessing and give me the compressed data automatically. Yay.
This also means that with only 4MB+4MB of VRAM for an animation for a character, I could double the characters, and have both genders without running out of VRAM. This also makes the game more usable on low end PCs. Now the limit is my art budget, rather than the hardware :D Anyway here is a reminder of the final video, and also that this is for my car factory game: Production Line