To be able to work efficiently with effects, is there a good way to combine multiple reusable "base" effects to achieve a more complex final result?

For example, say I have an effect that handles multi-texturing and one that handles ambient lighting. I'd like to combine them to achieve a final "ambient-lit multi-textured" effect.

This seems troublesome unless effects somehow can share information. When I just use the multi-texturing effect, I get the result I want. But when I afterwards apply the ambient lighting effect to intensify the color some value, it uses just uses the color from the ambient effect.

Is there a way to solve this? So that each effect can be individual and somehow not screw up the other effect? Or do I have to resort to manually writing one large effect file that lacks any flexibility?

  • 1
    \$\begingroup\$ One solution is to transform the whole scene with Post-Processing, but this doesn't seem like "lightning" it just seems like enhancing the final image and not really a "solution". \$\endgroup\$
    – Deukalion
    Commented Apr 5, 2013 at 9:44

1 Answer 1


You have two broad approaches.

  • You can use multiple effects, making multiple draw calls per model and switching effects between each. This is straightforward and would probably handle your simple multi-texturing and ambient shader combinations, and others like it, fairly easily. The trick is that you have to adjust the pipeline's blend mode between each shader (and possibly also modify the alpha outputs in the shaders) in order not to simply stamp over a previous draw's results. Because blend modes are not very configurable, this becomes constricting very fast.

  • You can use a single effect, with everything you want it to do combined into a single shader. This is basically what you were assuming was the answer, but you can do it in an achievable fashion. The trick is to split up the effect early, before it is an actual shader.

The first approach is cumbersome and I won't speak on it further. The second approach is more common these days, and there are a variety of ways to implement it.

The super-shader (or "ubershader") approach was popular for a while. This method basically involves you, the programmer, writing a single massive shader that "does everything," but with particular bits of the shader's functionality wrapped in preprocessor macros or some other kind of guard. For example:

color = input.color;
color = ComputeDiffuseLighting(color, normal, light_position);

color = (0.5 * color) + (0.5 * ComputeTextureColor(texture_coordinates))

Then you can create multiple actual shaders by recompiling this super-shader with various pieces turned on or off (you can either brute-force all permutations or you can intelligently preprocess all your materials to determine which combinations would be required). This way you always have a broad overview, as a programmer, of all the possible ways in which your different shader features would interact, but you also don't have to manually re-create tons of variations on very similar shaders. Here's another paper on the subject.

These days the hot trend is visual shader graphs and editors, such as ShaderForge for Unity.

enter image description here

The basic premise behind this approach (which is my favorite, with some caveats), is similar to the super-shader approach in that you are ultimately one final shader out of pieces. Instead, however, you the programmer author each small shader fragment independently, usually in some intermediate language or data structure of your own choosing (so you can understand the input and output variables of each fragment, and so on). Then you create a tool that allows an artist (or yourself) to string together these isolated fragments however you choose, and you post-process that graph (usually via a topological sort) into a final effect file you can feed your graphics API.

McGuire's Abstract Shade Trees paper is a good read on one way to build a shader tree system.

An interesting point about the shader tree approach is that you can choose the granularity of the nodes in your system. In some systems, nodes are almost always simple operations like "add" or "interpolate" or similar. In others, they can be very high level such as "bump map" or "cel shading." Or you can choose an in-between approach. The technique is particularly well suited to data-driven iteration, which is really nice, but it also requires a much higher up-front investment of time and technology before you can really utilize it, especially compared to the super-shader approach which can be done with a few crude regular expressions or text processing code fragments even if you don't have an actual C-style preprocessor available to you.

  • \$\begingroup\$ IMO, the node graph / shader tree system might actually offer too much control for some use cases. It's good for prototyping, or for VFX where you want the artists to have as much freedom as possible, but I think the ubershader approach is more appropriate for basic materials and lighting, where you want more structure. It doesn't make sense, for instance, to have an "ambient lighting" node that can be hooked up to anything - if it's enabled, it's supposed to be at a fixed point in the shader, and be hooked up to other things in a fixed way. \$\endgroup\$ Commented Dec 5, 2013 at 8:52
  • \$\begingroup\$ That's very true; the ability to have a huge range in the granularity helps this, I think (I find that systems where every node is exceedingly tiny, like an add or multiply, are too cumbersome to use). But so are systems where every node is very complex, because then your flexibility is limited. There is definitely an art to finding the proper balance for a team, and that takes a lot more effect than the ubershader approach. \$\endgroup\$
    – user1430
    Commented Dec 5, 2013 at 15:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .