Translucency and multi-pass lighting

Hi!

I want to render some meshes with translucency and multiple lighting passes. I compute the contribution of each light with a relatively big GLSL shader, and blend it with what already exists in the framebuffer. For fully opaque meshes, this requires rendering a black pass first, then blending with GL_ONE,GL_ONE. It works, and quite well.

But when rendering translucent meshes, there is a problem. For each such mesh, I first render it with color writes disabled and alpha writes enabled, to get the desired alpha value into the framebuffer. Then I enable blending with GL_DST_ALPHA,GL_ONE_MINUS_DST_ALPHA, enable color writes, disable alpha writes, and render the mesh N more times for N lights.

Let C_n be the color computed by lighting pass N, let C_b be the color in the framebuffer before blending, and let A be the destination alpha value. For one light, the final color is

C_1A+C_b(1-A)

This is correct. But for two lights, it is

C_2A+(C_1A+C_b*(1-A))*(1-A)

which is not correct, because this blends C_1 with the factor (1-A)*A, not A, and it blends C_b with (1-A)^2, not (1-A). It should be

(C_1+C_2)A+C_b(1-A)

instead. The two lighting passes must be summed up BEFORE blending them with the framebuffer. I also looked at other blend modes, but so far I couldn’t find any combination that fits.

And here lies the problem: I can’t sum them up in the framebuffer like I do with opaque meshes, because colors in the framebuffer need to be preserved for blending. I see that current games have translucent polygons and they are correctly lighted (and shadowed), so there must be a way to solve this. But how?

If anybody knows how to do this, and would care to enlighten me, I would be really, really thankful!

The key is to use different blending mode for the first pass than for the additional passes. For example when you have four lights you wish to calculate following equation:

((C_4+C_3+C_2+C_1)A)+(C_b(1-A))

That equation can be rewritten as:

C_4A + C_3A + C_2A + (C_1A + C_b*(1-A))

So what you need to do is:
Draw light 1 with: GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
Draw rest of lights with: GL_SRC_ALPHA, GL_ONE

Of course there is still problem if the mesh is concave and one visible triangle overlaps different visible triangle. In that case this will not work correctly even if your mesh has triangles sorted in such way that they render in correct order.

Arrgh! Forget my blabbering. Just ONE minute after I disconnected from the internet, I suddenly saw it. The solution.

I wanted to have this:

(C_1+C_2)A+C_b(1-A)

This is equivalent to

C_1A+C_2A+C_b*(1-A)

Writing it a bit different:

C_1A + C_2A+C_b*(1-A)

I realized that the part after the first + is nothing else than a blend of C_2 and C_B with the blend mode GL_DST_ALPHA,GL_ONE_MINUS_DST_ALPHA. Adding C_1A to this term is also easy. If C_2A+C_b*(1-A) is abbreviated X,

C_1A + X1

is just a blend of C_1 and X with the blend mode GL_DST_ALPHA,GL_ONE. Repeat for more lights. I implemented it, tested it, and it works flawlessly.

Now, the thanks have to go to myself for this time :slight_smile:

Out of curiosity, are you doing A:

for each light
 for each object

or B:

 for each object
  for each light

It seems to me that while it would blend the top object, the object behind it wouldn’t get shaded in subsequent passes if you’re doing option A. This is the problem I had hit and have since resorted to a “psuedo-sortless” technique.

Kevin B

I’m doing B. I dropped considering A really soon after it ocured to me that it would not only be incorrect but also slower: material state changes are more expensive than light state changes, I would not want them in the inner loop. Also, building a list of lights per objects is easier (=faster) in my renderer than the reverse.

I’m curious why you think A is incorrect? I’m guessing you misunderstood what I said? To be clear, I’m referring to how you treat all objects, not just translucent ones.

Kevin B

Well, you said that behind objects won’t get shaded. This is what I would call incorrect.

i’m having the same problem but i’m not sure what exactly i have to do now. this is how my renderer works:

  • pass 0 renders depth and ambient light
  • glBlendFunc( GL_ONE, GL_ONE )
  • passes 1 to x render all geometry for each light source
  • glDisable( GL_BLEND )

what exactly do i have to do now? do i need to enable blending for pass 0 as well, which modes do i need for passes 1 to x?

thanks in advance…

Vexator, do you mean for opaque or translucent materials? For the opaque case what you say should be sufficient. For the translucent case, I render the geometry with glColorMask(0,0,0,1) in pass 0, so that the right alpha is written into framebuffer but the color is not touched. This obviously requires a framebuffer with an RGBA pixelformat. Then I set the blend mode to GL_DST_ALPHA,GL_ONE_MINUS_DST_ALPHA for the first lighting pass, and to GL_DST_ALPHA,GL_ONE for the remaining lighting passes.

i tried what you suggested, and the desired mesh IS drawn translucent now - but all that can be seen through it is the clear color… not the geometry behind :stuck_out_tongue:

i set the clear color to grey and took a screenshot: www.vexator.net/blending_prob.jpg

(the muzzle flash is mesh with the alpha mask.) thanks!

This looks like a classic case of bad sorting. It looks like you’re rendering your transparent objects first. Thus, when the objects behind them go to render, the fragments are being z-tested out and discarded.

Kevin B

i’ve checked that already, the muzzle flash is last in the queue. :stuck_out_tongue: