I think about mid 2012, I was realizing we had a number of duplicate textures we were using. I thought an appropriate solution was to add a form of scripts that could redirect the loader to a pre-existing texture instead, to save us from having hundreds of duplicates. This was just a side idea of mine but I thought it would make things much more efficient, especially when defining multiple skins for models.
This is often how most additions to the engine come about, first I look for a problem and then figure out a solution; the implementation is often when things get expanded out depending on how difficult the implementation turns out to be. To my surprise, this was relatively easy, so the system got spanned out quite drastically compared to what I originally had in mind.
For starters, I didn’t want to copy an existing implementation, such as Quake 3’s materials (or “shaders” as they’re called) because, by general rule of the thumb with Katana, is that I try to implement everything myself in the hope that I can utilize it better for our own uses, but also not be constrained or limited by already existing implementations.
It actually wasn’t until last year, 2014, that I finally got round to implementing it and it turned out surprisingly easier than I thought. Or rather, the initial implementation did! One of the things I spent a lot of time thinking about, was the syntax. Initially I decided to try doing my own thing; first the initial section which was filled with sub-sections that represented each skin.
The ordering of this was completely handled by the rendering pipeline and you had no control over the individual properties of each texture added to the material. There were no flags or other information you could utilise. It was rubbish, but for a while it did the job pretty well. That all said however, I quickly learnt that this was hard to understand, in some sense, and not quite as expandable in the long-term as I desired. I ended up throwing out the implementation in the end and rewrote it in, what I hope, is a smarter and more expandable manner. The new syntax introduced a number of new features.
- Contextual functions that function differently depending on where they are used within the script.
- Complete control over each texture layer of the skin and their order.
- Support for texture rotating and scrolling.
- Control over different texture blending modes.
- Flags for skins, textures or globally.
- Global flags for alpha-testing, blending and preservation.
- Access to internal textures.
- Global material names.
The only problem afterwards was updating all our materials. Luckily though, Maik, Decay’s level designer, quickly produced a small application which would recursively go through the directories in the materials folder and produce materials with the updated syntax; sorted!
Unlike my initial implementation however, there was a lot that had to be overhauled to achieve it. For starters, the engine was expanded to support additional layers for multi-sampling which weren’t necessarily in an expected order, since the scripts now had complete control over it (support for multi-texturing was also made a requirement). As a solution for this, a tracking system was introduced, that’s dynamically allocated on startup depending on the number of supported multi-sample layers, which keeps an eye on what TMUs are active, what texture is assigned to them and the current blending modes.
The actual rendering of the material itself however, is hardly elegant and there’s lots of room for improvement. Essentially, there’s both a pre-pass and post-pass for materials which is done when drawing an object, this iterates through each texture within the currently active skin of the material and ensures that each TMU is set up appropriately. Currently the second TMU is always reserved for the lightmap, though this is just a leftover from the original implementation and will very likely change, only remaining for cases where no lightmap slot is declared within a material. Detail texturing is currently done by just multiplying the original UV coordinates by a customisable console variable, which I would like to change in the future so it’s not reliant on the current UV map of the object. Generally, as well, for any UV manipulation we can end up iterating through object vertex lists multiple times which really needs to be improved.
In addition, as I’m sure some people know already, I also developed a WYSIWYG editor for materials which the engine integrates into. The editor is a powerful tool, because it not only renders materials as they would appear in your game, but your changes can be reloaded on the fly so that there’s no reason to manually load up your material again, meaning there’s quick iteration and instant feedback.
There’s a few things I want to explore in the future and expand upon, such as being able to declare variables within the scripts. I’ve also thought about materials as binary files that would exist alongside the scripts, the material would automatically generate the binary files if one doesn’t exist and load it instead. Additionally I would like to implement my own texture format that could contain information necessary for particular textures, for example how they would be rendered in the world, how many mipmaps they might support and other information like that (very similar to Valve’s VTF format, for those familiar with it).
Thanks for reading!