I love nothing more than a good dungeon loot crawler. Despite my best intention to keep things simple on this project, I just had to add a loot system. Coding the feature wasn’t that big a deal, however, that isn’t where the cost lies; the sheer number of assets needed is. After all, I wouldn't play a loot crawler if every item looked the same. I needed to cut down on time per an asset fast.
There are a lot better artists out there than me, so I'll keep the actual modelling and Substance work-flow description as short as I can. The real meat of the post will be about scripting Substance Designer, and finally, adding it into a game engine pipeline.
This is a fairly chunky blog post, so feel free to skip ahead to any areas of interest. Otherwise, TL;DR: magically textured models in game thanks to Substance Designer.
To cut down on modelling time, I decided I wouldn't have time to build high poly models for each individual item. Instead, I created a single texture that has a bake of all the details I want on my item's surfaces (well, almost all, currently the sheet is mostly empty, but I'll fill it out over time).
This texture set contains:
The texture contains no details that give reference to a specific scale, and as such, I can unwrap with disregard to Texel ratio. The idea is that as I'm modelling something, I can unwrap over the top of the height map to give additional panel edges and details that I would otherwise have to model into a high poly (which would significantly increase time needed per an asset).
In order for my unwrapped models to play nice with Substance Designer I gave them an additional UV channel. The channel contains a one to one UV layout with consistent Texel ratio. The basic idea is that I transfer both the Material ID map, and the height map over to the new layout using Substance Designers built in baker.
After this, Substance Designer takes over:
All of this is pretty standard Substance fair. However, I was really happy with the final result in my test render. Further more, the assets were fast to make.
I sat down to add all the assets into the game and grab some screenshots!
All I needed to do was:
That's not too bad right? Well, currently I only have 12 or so assets, and only 1 Substance I need to apply to them. So I sat down and got started! I made it about 3 assets in before my ADD kicked in. If I can't even get through 3, how am I going to go when I have over 100 models with multiple substances I want to apply to them. It just isn't going to happen, even with the help of Designer, this feature is already too expensive for me.
Further more, what of my poor Git Repository? All of these textures are data that I would have to version control (they take ages to set up, so I really need to back them up somehow right?!) What if I tweak my Substance, now that's another iteration in Git. My repository size is going to explode with this approach. Additionally, I'm pretty sure I'd go borderline insane managing it all.
The first problem I needed to deal with was all the Substance Designer set up I require for every model. The kind guys over at Algorithmic provide some batch tools for doing everything you need with Substance Designer through the command line. So first steps are to wrap the command line utilities in functions that can be accessed from Lua (spoiler, I chose Lua for this so that it's accessible from my build pipeline). You could wrap up the tools directly in Lua, I just felt like doing it in C# at the time.
The first functions I had to wrap up were all of the baking utilities. I’ve shown a specific use case for one of them, but including all of them would make this already rather long post unbearable. To figure the rest out run “sbsbaker.exe –help”.
Most of the helper functions are for managing file paths on disk, and aren't really worth talking about. The only confusing one is the GetBakeSize function. This is because the batch tools use a numerical code to represent texture size. Even more confusing, the codes are different from the sbsbaker tools, to the sbsrender tool. I'd rather not have to remember anything to do with this, so I've built a function that takes a size in pixels, finds the nearest power of 2, and then returns the appropriate code for baking. I have another that does the same thing for the rendering side of things. Here's the mapping for pixel size to code for both baking and rendering:
Once I wrapped all of the bake functionality, the next step was to hook the baked textures into an existing substance. The batch tool appropriate for this task is the sbsmutator.exe. I've written a C# friendly version of the command, and then created an additional Lua friendly one that accepts a list of connections as a table. This command will basically create a new substance file, with all of the textures you've listed connected into the appropriate slots. I generally try to keep the substance file in 16bits to keep precision when creating normal maps from height maps. Unfortunately, the sbsmutate tends to set everything up as 8bit. I've not yet found a way of setting this through the mutate command, so I've resorted to hacking the XML myself (if anybody at Allegorithmic or otherwise is reading this and knows a sbsmutator based solution, please hit me up on twitter with it, and I'll update my blog).
After mutating the substance you need to “Cook” it (which converts it into a packaged binary format ready for the renderer using sbscooker.exe), and then to render it (using sbsrender.exe). This step outputs your final texture maps from the substance file. When cooking the substance you have to include the path of Substance Designers own library, and any other libraries you may be working from. I found the order of the command line argument was pretty finicky with the cooker, and this was the particular magic I settled on that worked:
The last step is to wrap up the function for Lua access. I'm using nLua for this, which makes the whole process incredibly painless.
With the batch tools completely wrapped up for Lua, it isn’t terribly hard to write a script that spits out everything the game needs. However I’m still stuck with the problem of version controlling and importing it all.
I've previously talked about the build pipeline before in my “Getting Started” blog post, and I made it pretty clear that one of my core pillars is “Do not version control built data”. Typically you wouldn't add your EXE files to your repository. You'd add your code files, and build the damn binaries. So why do any different with art. It's kind of a pet peeve of mine. I've seen people version control a DDS file, and forget to add the photoshop file. Later they quit, their PC gets formatted, usually on that day it's suddenly mission critical, “We can't possibly ship with that blood in that texture in JAPAN, HOLY HELL WE ONLY HAVE HOURS UNTIL FIRST SUB!”
So, what I already have is a Lua scripted build pipeline. My assets are added to the build via script, this creates a dependency tree that then resolves any dynamic dependencies, and builds everything in the correct order. This system is used to build all my game data, and is the logical place to add in my Substance Designer Pipeline. The example above shows a build for a typical model. It has dependencies on the source model, and materials. These in turn have dependencies on shaders and textures. The build pipeline also has the ability to save out information for the game to access. It can, for example, tell the game where to find all the models to use for generating weapons and items.
There are however a few missing pieces:
All of this has allowed me to define a graph that takes a model, and a Substance file, and directly build the data that the game loads. It’s an easy step from there to define all my weapons and armour as a list of models, and a list of substances. The pipeline then builds every model against every substance, and saves out a list of what is available for the game to use. It was beautiful… Except it took 30 minutes to run my build pipeline now (it used to be 15 seconds).
This brings me to one of the main reasons for setting this all up in a dependency tree; you can save the state of the tree for later use. If the inputs to a node don’t change, then the output will be the same. So I saved all of the inputs, and a list of all the hashes of the output files for each node. This allowed me to check which nodes I needed to build, and only build the data that has actually changed. With this set up, the second run of my build pipeline was down to 10 seconds flat. Awesome, now I’m back into a reasonable iteration time for editing and testing data. If I was a multi-person studio, I would save the interim and game data on a shared network drive allowing other people to benefit from previous builds on my machine.
All of this has left me exactly where I need to be. No textures need to be version controlled (they get built by the pipeline, so no need). Additionally, all I need to do to add a model to the game is add it to a list. After that the pipeline takes over, and magical substance things begin to happen.