Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

In Direct3D, multipass shaders are simple to use because you can literally define passes within a program. In OpenGL, it seems a bit more complex because it is possible to give a shader program as many vertex, geometry, and fragment shaders as you want.

A popular example of a multipass shader is a toon shader. One pass does the actual cel-shading effect and the other creates the outline. If I have two vertex shaders, "cel.vert" and "outline.vert", and two fragment shaders, "cel.frag" and "outline.frag" (similar to the way you do it in HLSL), how can I combine them to create the full toon shader?

I don't want you saying that a geometry shader can be used for this because I just want to know the theory behind multipass GLSL shaders ;)

share|improve this question
    
"In Direct3D, multipass shaders are simple to use because you can literally define passes within a program." No you can't. You can define passes in the FX file, but that's not the same as an HLSL shader. –  Nicol Bolas Mar 21 '12 at 9:38

4 Answers 4

up vote 6 down vote accepted

There is no "theory" behind multipass. There are no "multipass shaders". Multipass is very simple: you draw the object with one program. Then you draw the object with a different program.

You can use D3DX stuff like FX files to hide these extra passes. But they still work that way. OpenGL simply doesn't have a hiding place for it.

share|improve this answer

Render the object with the cell shader, and then re-render it with the outline shader.

share|improve this answer
    
So I should make two different shader programs? Why is it then that I can give a shader program as many vertex/geometry/fragment shaders as I want? There must be a way to do it with one program. –  Boreal Mar 21 '12 at 1:45
    
You aren't giving it multiple vertex(etc.) shaders. There is only one main function, so you don't have multiple entry points. It is very similar to a C program in that you can have multiple files which are linked together to make a program. You can share these files between different programs so you don't need to duplicate the code, but each file is not necessarily a program by itself. –  Chewy Gumball Mar 21 '12 at 3:12
    
That's the way to go, Render once with one shader pair (vertex + fragment) then render again with another pair (or use the same vertex shader + another fragment shader). Sometimes you render to a buffer and use (yet) another shader to 'blend' it into the final result. –  Valmond Mar 21 '12 at 9:24

There are some people who know a great deal about GLSL, so I hope they come set the record straight, but based on what I've seen, you should be able to, in your fragment shader, do something like this (pseudocode, I'll leave the actual GLSL up to the people who know it better):

out vec4 fragment_color
vec4 final_color
final_color = flat_color
If this fragment is in shadow
    final_color = shadow_color
If this fragment is an edge
    final_color = edge_color
If this fragment is a highlight
    final_color = highlight_color
fragment_color = final_color

By using ifs in this way, you get something like multiple passes. Does that make sense?

Edit: Also, hopefully this question on SO helps dispel some misunderstanding.

share|improve this answer
    
It's important to note that GLSL will execute all statements in the if branches every pass even if they aren't enable. Apparently just calculating everything and adding all the colors together but multiplying the values by either 1.0 or 0.0 to enable or disable them is quicker. –  David C. Bishop Mar 21 '12 at 8:20
    
@DavidC.Bishop: That is not true. At least, not guaranteed to be true. The compiler will decide whether an actual branch is faster or a "compute everything and sort it out later" approach is faster. –  Nicol Bolas Mar 21 '12 at 9:39
    
As an aside, the behaviour referred to by @DavidC.Bishop is hardly specific to shaders and the GPU. Modern CPUs executing normal programs will speculatively execute at least one of the branches if the tested value is not yet available. If they turn out to be wrong, they roll back and redo. This ends up yielding a big speedup on average. –  ipeet Mar 21 '12 at 12:45

In OpenGL 4.0 there are uniform subroutines. These allow you to define functions that can be swapped out at runtime with very little overhead. So you can make 1 function for each pass. Also 1 function for each shader type.

There's a tutorial here.

For older OpenGL versions your best bet is to either have a bunch of different shaders and swap between them. Otherwise you can add together values that you multiply by a uniform that is either 0.0 or 1.0 to turn it on or off. Otherwise conditional if statements can be used but OpenGL will execute all possible outcomes every execution/pass so make sure they aren't too heavy.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.