“Coffee Game” 1 is the working title of our work in progress WebVR coffee house management game. You will be able to make coffee, serve your customers and make money to buy new machines. With the size of this game being above the average WebVR game out there, we were required to do some custom optimization to ensure it runs smoothly on lower end devices.
- During the writing of this blog post, the game’s name was finalized to “Barista Express”!
The Café Scene
The scene the game plays in contains a lot of furniture and small decorative objects to immerse the user in his environment and the game’s setting.
As our team has limited modeling capacity, we decided on a simple low-poly and mostly textureless art style that even a programmer would be able imitate when making assets. In this case, Florian, the designer and developer of this game ended up also doing almost all of the modeling work himself! With the exception of a couple of models that we found on Google Poly or Sketchfab, which is the second benefit of this very popular art style.
Textureless means that every color is encoded in material parameters, resulting in a lot of materials. At the time of writing the static parts of the scene contained what is listed in the table above.
Draw call count spiked up to 200! This is not viable for most Cardboard devices and did not run well on Oculus GO or GearVR. And these are only the static objects! Thrashing the GPU with 200 tiny (because of the low-poly style) draw calls is not the way you want to roll.
Batching Before Export
Instead, how about merging all the meshes together in Blender before export? Since there are mostly no textures in the scene, the textures do not need to be atlassed and this could be very easy!
Joining all the meshes will greatly reduce the amount of meshes in the glTF file. There will still be one glTF primitive element per material which will at best get the number of draw calls down to 43. That is good, but we can do better.
Once you join the meshes in Blender, editing is way harder. While an option would be to create a script that joins all meshes into a joined mesh before exporting to allow keeping the unjoined meshes around, this is additional hassle you want to avoid for a good workflow.
Since the only difference between our materials is the base color, you will be thinking this is a great fit for joining all meshes and converting the materials into vertex colors.
And you would be right with this assumption. The reasons we went for a slightly more complicated solution is a small improvement in file size, easier scene editing workflow and easier viewing of the result in gltf viewers.
The glTF exporter for Blender exports mesh color data as three or four component float vectors, i.e. 12 or 16 bytes of data more per vertex. Since a lot of vertices will have the same colors, there is a lot of redundant information wasting space here. Instead we are going to use a PNG-like approach by saving all these colors in a texture and using a simple index into this color-palette rather than the full color.
Additionally, while vertex colors are supported by glTF, not every tool, viewer or rendering framework trivially supports it.
Finally, having the optimization as a separate automatic stage after export (think of gulp watching the export directory and automatically pumping the data through the optimization pipeline) rather than a manual one before is a nice improvement to development workflow aswell.
Suppose you collect all the different colors in your scene and create a texture from those. The texture will look something like this:
Similar to an index into a palette for colormapped PNGs we can now “index” into this texture using the texture corrdinates. By setting UV coordinates so that each vertex receives the color of exactly one of the pixels we achieve the same look with a lot less data. Since we have less than 64 different UV coordinate values, we can even get away with using normalized unsigned short data for the texture coordinates. (Normalized unsigned byte data for texture coordinates is not supported by glTF as the coordinates needs to be 4-byte aligned!)
We implemented this optimization using gltf-pipeline. “Gltf Pipeline” is a nodejs based command line tool that optimizes glTF files, converts them to GLB and more.
|File size||1673 kb||1747 kb|
After running the static scene through the pipeline with our custom stage, we ended up with the improvements you can see in the table above.
The remaining draw calls and primitives are due to transparent/alpha-blended meshes not being merged into the static mesh batch. This way we can keep drawing the big batch with an opaque rendering pipeline and avoid issues with drawing-order for the transparent mesh parts.
The 74 kb increase in file size is owed to the added texture coordinate data and meshes that appear multiple times in the scene having their geometry duplicated.
How To Use
Some of the changes we made to gltf-pipeline will be contributed upstream to the original repository. But the palette mesh optimization does not fit, as it is very specific to the art style.
We open sourced the code for this custom stage for gltf-pipeline on Github vhiterabbit/gltf-pipeline-stages. You can checkout the repository and follow the example in the README. More custom stages will eventually follow!
I hope you enjoyed the read and our code helps you build bigger games in the future. Be sure to follow us on social media to catch notice of the release of “Barista Express” in the upcoming weeks—or subscribe to our newsletter.
Have fun building VR browser games!
Jonathan of Construct Arcade.