modularity in shaders

Hi,

I have a question about modularity in writing shaders.Lets take following scenario :

We write a nice simulation game with lots of
visual effects using shaders.Then we decide to
add fog to our simulation.Obviously fog should affect all objects in the scene so we need to modify all our shaders.We have to have one set
of shaders with fog and one set without fog.
If we want to add other effects which will affect entire scene then we go to a combinatorial explosion!

So the queation is : What does GLSL provide
to solve this problem? Shall we use brute force
or is there a clever way to write extendible
shaders?

Thanks in advance

You could define a function:

void FogPixel(inout vec4 colour, vec3 position)

and when you create your shaders on the fly, if you’re not doing fogging, insert a string:

void FogPixel(inout vec4 colour, vec3 position)
{
// do nothing, passthrough
}

and when you want to do fogging, insert a different string:

void FogPixel(inout vec4 colour, vec3 position)
{
// fog implementation
}

So that the fog function is behaving sort of like a virtual function / abstract interface that you can call in your materials without worrying about the implementation.

If you look in GPU gems, there’s an article about interfaces in Cg, which just formalizes the above.

Unfortunately, I’ve tried something like this before, and its really slow… compiling a one line glsl shader takes about 40ms on my machine, compiling 6 different (short) shaders took close to 1 second. I think nvidia needs to optimize their glsl compiler more…

So if you going to go with the compile-on-the-fly route, then you’ll need some method to cache your shaders, or perhaps you build them all ahead of time at level load, rather compiling them on the fly…

Also, if you do things this way, you’ll run into inefficiency problems. What if in your FogPixel() function you sometimes want to use linear fog, and sometimes radial distance fog? What if by chance you’re already calculating the radial distance in your main shader, should you have to calculate it again in your fog shader?

GLSL allows you to assemble shaders out of pieces. You pass it an array of char pointers, and their respective sizes. The compiler will compile the union of all of these pointers.

Thus, you can have one string fragment per function prolog (varyings, etc), one string fragment per kind of light or other color processing, and one string fragment per final fragment processing (such as fog).

Then run through your material description, and collect the right fragments for your situation, and call ShaderSource() with the thus-assembled shader. You probably want to cache the specific result of assembling specific fragments, so that you don’t have to re-assemble shaders every frame :slight_smile:

Also, it helps if you make conventions for your fragments, like “the incoming and outgoing color always lives in a variable called ‘color’”.

Sh has some cool operations that allow you to do things like this easily. For example you can put a global variable fogType in your code. Then in the shader, have code like:

switch(fogType) {
case linear:

case none:
… etc.
}

By simply changing the value of fogType in the code and recompiling the shaders, you can change your fog type. Since fogType is a constant as far as the program is concerned, the appropriate fog calculation will get compiled into your shader (ie no conditionals). It gives a very clean interface between c++ and shader code, which lets you avoid doing stuff like manipulating code as strings.

Although Sh is still in development, it is evolving very quickly and is well worth checking out.

(I know your question was about glsl, please forgive me)

IF you are not geometry bound go multipass, it could be Z first pass too.

Thanks for answers.

@pragma: GLSL has #ifdefs, so you can easily pre-pend a line to your shader that says #define FOGTYPE LINEAR, and then do something equivalent to the switch statement with #if.

If shader compilation time is not a problem, then using #ifdef is the best solution.