It is very easy for programmers to be seduced by pretty tech. Case in point: graph-based shader systems. Unless you’ve been sleeping under a rock, you must have noticed that these systems have become haute couture in the rendering world. Just so we’re clear, what I’m talking about are systems where you can construct shaders in a graphical UI, stringing together Lego-like building blocks of shader code snippets, with connecting lines. Like this:
It’s possible the trend started with Unreal’s Material Editor (featured above), but Epic Games certainly didn’t invent these systems. This functionality has been around for a long time in various software packages, like e.g. Maya’s hypershade. You can find several different graph-based systems neatly illustrated here.
These systems are certainly very pretty, they’re absolutely cool tech, and they quite powerful in that you can basically piece together any type of shader functionality you would like. But, therein lies the problem, and why I think they’re absolutely the wrong way to go, at least as long as performance still matters. You see, the problem is that these shader systems are full-fledged graphical programming languages, and exposing a programming language, whether graphical or not, to a nonprogrammer is rarely the right thing to do. There are exceptions, of course, such as scripting languages for designers, but even there the exposed functionality should be as limited as possible. (Yes, programmer guy, this means that exposing LUA to your designers is bloody retarded on your behalf.) However, when we’re talking something as performance sensitive as fragment and vertex shaders, it’s outright criminal to have nonprogrammers do the programming.
Those who know me might think this is a departure from my usual “empower the user”-spiel, and since subtle points are usually lost on people, let me hammer this point home. Don’t get me wrong, graphical shader systems are great for offline rendering (film), for rapid prototyping, or other scenarios where iteration time is much more important than runtime performance. For games, however, where we ultimately have to meet a frame rate goal of 30 or (ideally) 60 frames per second (or 20, if you’re crap), there is simply way too much performance that will go lost in a system like this.
At the end of the project when you truly notice how out of hand the shaders have become, how do you reel things back in to where they should be? Not very easy when there are custom graphs (read custom code) for every single material in your game (of which you have many hundreds). How do you provide shader LOD by dropping features when there really aren’t any predefined features (as e.g. someone could have reimplemented that parallax-mapping “box” behind your back, so removing the box doesn’t do you any good)? Just to name a few issues.
So Christer, you say, what’s the alternative? What do you use, huh!? Well, though it is hardly without problems of its own, we opted for an übershader approach for our engine. By this I mean that we have a small number of artist-selectable shaders, which each contain a large number of code branches corresponding to features (whether to do environment maps, parallax mapping, emissive, etc). Behind the scenes (in the tools) we create permutations of these top-level shaders depending on checked features as well as on other variables, to generate branchless shaders that actually end up in the game and that run “at speed.”
The fact that we use an übershader isn’t important though, we could equally well be gluing code pieces together similar to what happens inside one of the graphical shader tools. What is important, however, is that our supported features are effectively being exposed to the artists as checkboxes (and a set of associated controls, like sliders, color pickers, etc), not as a do-whatever-you-want interface.
To be clear, our Maya-based system is very unsexy (in fact, our artists would probably happily attest to it being outright ugly), but its salient features are that people can get things done using the system and, most importantly, this system allows us to “uncheck checkboxes” in the tools to implement shader LOD or to scale back overuse of features that in the end turned out to be much too expensive to support. (BTW, for any SCEA SM artists reading this: we want to revamp that Maya interface eventually, honest!)
You rarely hear people say bad things about these graphical shader systems, but next time you’re at GDC and have the opportunity to meet up with a bunch of developers who’ve used a graphical shader system, ask them how much work they spent on cleaning up shaders at the end of the project. The stories I’ve heard haven’t been pretty.
Today’s moral: don’t make nonprogrammers code!