Best way to handle multiple "flavors" of the same GLSL fragment shader?

Hi folks,

I'm working on some engine-y kind of stuff (although not quite a full engine). I wrote a resource manager and a loader for .obj and .mtl files and can currently draw 3D models to the screen. Pretty neat.

The problem I am having is that not all .mtl (material) files have the same "variables". For example, some may have a reference to a normal map file, while others don't. Some may have specular constants and exponents defined, while others may not, and others still may define constants and textures for them.

Separate from the differences in materials, the number of lights affecting a mesh may differ from object to object.

Right now, my shader is just reading the diffuse texture and ignoring everything else, but obviously I want to be able to handle all of the other possibilities.

I see two options to handle all of these combinations of scenarios:

1. Have a "god"-shader that has some max # of lights (say, 5) and handles everything the .mtl file may have: ambient, diffuse, specular, specular exponent, and normal maps. If the actual material being rendered didn't have one of those terms in the .mtl file, (a) use a "default" value for it in the shader, or (b) possibly detect that it isn't in use and don't include it in the calculation. I don't like (b) so much because that would likely require a lot of branching, which is bad news.

2. Have one shader per configuration. So one shader that handles ambient, diffuse, and specular without any texture maps. One that handles it with texture maps. One that handles it only with diffuse texture map. For each of the previously mentioned, have versions that handle 1, 2, 3, 4, and 5 lights. Etc. etc. etc. Obviously this could get out of control pretty fast, but it seems to be the best as far as avoiding unnecessary computational costs. I am leaning towards doing things this way and writing some sort of custom shader "template" that at run-time can be transformed into the exact shader I need depending on the configurations of the material and # of lights.

I have searched pretty extensively and couldn't quite find an agreed-upon answer for this. I know that Unity allows you to write shader "templates" a la option 2 and uses #ifdef statements to compile the various parts in or out. However, if anyone here with more experience has any other suggestions to offer I would love to hear them.

Thanks a bunch!

Edited by Andrew Smith on Reason: Initial post
Unless you are making very generic Unity/Unreal like engine I suggest avoid complex shaders and just create only few simple ones.

Do you really need to have such diversity in materials (with normalmap or without, with specular or without)? Just handle few simple variations manually. I suggest to think about creating "dummy"/default material properties or textures. For example, for normalmap just create normalmap with unit vector up and use it for materials that don't have normalmap. For specular similarly - tune variables/textures so shader output looks exactly as without it (like multiplying with 1 or adding 0).

Also if statements are not very bad news. This is not 2005. Depending on calculations they can be very cheep. Just a simple mask + and/andnot (as seen in HandmadeHero).

As for lights - I suggest to look at deferred rendenring techniques. They allow to calculate scene geometry only once and then light it up in separate pass for lights. There are various modifications that makes their rendering much cheaper when light count is varying, for example, forward+, tiled deferred, etc...
Hey Mārtiņš, thanks for the good advice.

After considering it, I do think I agree that I can probably get away with using default or "identity" kind of values for a lot of the operations. Even for lighting I think I could just limit each object to passing the N (say, 3) most influential lights into the shader and using 0-intensity lights if I don't have 3 to pass in.

I don't have very grandiose ideas for what I want to accomplish with this project, it is mostly just a sandbox for me to concretely implement a lot of the game-engine concepts that I have been studying, so this should suit my needs.

I suggest to look at deferred rendenring techniques

I am familiar with the theory of deferred rendering, but haven't ever implemented it. It might be something I get around to doing once my engine is in a more robust state.

Edited by Andrew Smith on
A more general bit of advice, obj is a horrible format for meshes, for one it's a text format, for another it indexes the attributed individually which no modern graphics api can do without a performance penalty.

So it requires that you parse text and reorder the data before you can pass it on to the renderer.

Also if the branch in a shader is decided by uniform data (data that is the same for the entire draw call) then there is no (or very small) cost to it.
ratchetfreak
for one it's a text format

This is why I chose to use it. It's the simplest case that I can get up and running with since it is simple enough for me to write my own loader. But for what it is worth I am planning on writing the data into a custom binary file after I load it and having my mesh loading code check for that file first before reading it in from the .obj. This should definitely speed it up (because it does take a couple seconds to load .obj's with thousands of tris).

ratchetfreak
So it requires that you parse text and reorder the data before you can pass it on to the renderer.


This isn't too big of a deal for me. I store the loaded mesh in memory anyways as an array of vertices and an array of indices.

ratchetfreak
Also if the branch in a shader is decided by uniform data (data that is the same for the entire draw call) then there is no (or very small) cost to it.


I didn't know this, but it does make sense that the GPU could optimize this. Good tip.