multiple lighting passes and transparency

When I have to draw some transparent stuff, I save it to the end of the pass and draw it with glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ). That is, roughly speaking, if b is the base (opaque) color at a particular pixel, and t is the transparent color being rendered there, the result comes from the formula

alpha * t + (1 - alpha) * b .

However, sometimes I need to use more than one pass to handle more than 8 lights or to make lights cast shadows. In that case, passes other than the first use glBlendFunc( GL_ONE, GL_ONE ).

My question is, how do I combine transparency with multiple lighting passes? If b1 is the opaque color produced in the first pass, b2 is the opaque color produced in the second pass, and so on, then I want to end up with something like

alpha * (t1 + t2) + (1 - alpha) * (b1 + b2) .

But if I just blend in the transparent stuff at the end of each pass, I’ll end up with something like

alpha * t2 + (1 - alpha) * (alpha * t1 + (1 - alpha) * b1 + b2)

which has too little contribution from the first pass.

The only approach I can think of that might work involves rendering transparent stuff to a texture at the end of each pass, and then blending that texture with the opaque pixels at the end of the frame.

Use a shader. You can have as many lights as you want in a shader. Well, as many as you have uniforms for.

Alternatively, don’t use more than 8 lights on a single object. Rare is the scene where more than 8 lights will actually have a noticeable effect on an object.

When you have

a * ( l3 + l2 + l1 )  + ( 1 - a ) * b

you can rewrite that as

a * l3 + a * l2 + ( a * l1 + ( 1 - a ) * b )

So the first pass will be GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA and the following passes
GL_SRC_ALPHA, GL_ONE. Of course you need to apply all passes to one transparent object before you move to the next and if two parts of the object overlap, the color will be not correct in the place where the overlap happens.

Thanks for the reply, Korval, but…

Use a shader. You can have as many lights as you want in a shader. Well, as many as you have uniforms for.
This assumes that the hardware supports shaders. Granted, the alternatives that I’ve thought of also require features that may not be present on old/cheap video cards.

Also, more than 8 lights is only one possible reason for using multiple passes. Another reason I mentioned is shadow volumes, AKA stencil shadows. I suppose it may be possible to do that in a single pass with a fancy shader, but I’ve never seen a published description of such an approach.

Alternatively, don’t use more than 8 lights on a single object. Rare is the scene where more than 8 lights will actually have a noticeable effect on an object.
I can imagine cases where more than 8 lights do affect a single large object. It seems like a lot of work to figure out which lights significantly affect which objects, and to keep changing the light configuration during rendering. If I’m going to do a lot of work, I want a result that’s always correct, not just sometimes correct.

Komat, I don’t understand how your formula relates to mine. What happened to the b2 term, the opaque contribution from the second pass?

Originally posted by James W. Walker:
Komat, I don’t understand how your formula relates to mine. What happened to the b2 term, the opaque contribution from the second pass?
For the multipass approach to work without temporary textures you need to render the geometry using something like:

foreach light {
   foreach opaque_object influenced by light {
       if ( first rendered light ) {
           disable blending
       }
       else {
           blend ONE, ONE
       }
       draw object.
   }
}

*

foreach transparent_object {
   foreach light influencing object {
       if ( first rendered light ) {
           blend SRC_ALPHA, ONE_MINUS_SRC_ALPHA
       }
       else {
           blend SRC_ALPHA, ONE
       } 
       draw object.     
   }
}

Basically b in my equations equals to value in framebuffer at point marked by * which is b1 + b2 in your equation.

There is one trouble with this approach. The structure of the loops complicates use of shadows on transparent objects, especially if stencil based shadows are used and costs more memory for the shadowmap based ones.

Thanks, Komat, I understand better now.

Originally posted by James W. Walker:
The only approach I can think of that might work involves rendering transparent stuff to a texture at the end of each pass, and then blending that texture with the opaque pixels at the end of the frame.
I spoke too soon about this idea. If transparent geometry can overlap, I can’t come up with any blending functions that would make this idea work right.

If transparent geometry can overlap, I can’t come up with any blending functions that would make this idea work right.
That’s because there aren’t any (as far as I know). The math only works for a single additive accumulation. Once you get overlap, the math either doesn’t exist or becomes much more complex.

There are reasons why multipass and transparency are to be avoided.

Korval:
[quote]If transparent geometry can overlap, I can’t come up with any blending functions that would make this idea work right.
That’s because there aren’t any (as far as I know). The math only works for a single additive accumulation. Once you get overlap, the math either doesn’t exist or becomes much more complex.
[/QUOTE]This sounds like a job for premultiplied alpha:
Premultiplied Alpha (Forsyth)

This sounds like a job for premultiplied alpha:
Premultiplied Alpha (Forsyth)
That’s an interesting article, thanks for the reference.