Multipass Rendering #2

July 15, 2009

After thinking about it for a while, there is also another aspect about multipass lighting I always feared, and its really trivial to handle, just like fog.

Transparent objects are rendered the same way as fogged objects: the final shaded pixel is linearly interpolated with a color, just like fog, but this time the color comes from the destination pixel. If you do the math,or if you take a look at my previews post, it becomes clear that transparent objects can easily be multiopass lit. Both techniques can also be combined as well.

In the case of both transparent and fogged objects the final color is calculated like this:

final_color = lerp(dest_color, fogged_shaded_color, alpha)
<==>
final_color = dest_color * (1-alpha) + fogged_shaded_color * alpha
(1)

Let A = dest_color * (1-alpha), (1) becomes:

final_color = A + fogged_shaded_color * alpha (2)

where:

fogged_shaded_color = lerp(fog_color, P1 + … + Pn, fog_factor) (P1 = first pass shaded color, Pn = nth pass)
<==>
fogged_shaded_color = fog_color * (1 – fog_factor) + ( P1 + … + Pn) * fog_factor
(3)

Let F = fog_color * (1 – fog_factor), (3) becomes:

fogged_shaded_color = F + ( P1 + … + Pn) * fog_factor (4)

Combine (2) with (4):

final_color = A + (F + ( P1 + … + Pn) * fog_factor) * alpha
<==>
final_color = A + F * alpha + ( P1 + … + Pn) * fog_factor * alpha
<==>
final_color = A + F * alpha +  P1*fog_factor*alpha + … + Pn*fog_factor*alpha

Substitute A and F again:

final_color = dest_color * (1-alpha) + ( fog_color * (1 – fog_factor) +  P1*fog_factor) * alpha + … + Pn*fog_factor*alpha
<==>
final_color = lerp(dest_color, lerp(fog_color, P1, fog_factor), alpha) + … + Pn*fog_factor*alpha

“lerp(dest_color, lerp(fog_color, P1, fog_factor), alpha)” is a normal pass with fog and dest_color terms intact (not zeroed). So the first pass is performed normally.

Now let’s see what happens with the subsequent passes. They are of the form:

additive_pass_n = Pn * fog_factor * alpha

which is equivalent to:

additive_pass_n = lerp(ZERO, Pn, fog_factor) * alpha

Which is a little different pass, with fog_color = black as we already saw, the dst blend factor set to GL_ONE, as usually is the case with additive passes, but the src blend factor set to GL_SRC_ALPHA instead of the typical GL_ONE, because we want our shaded result multiplied by the alpha value.

So in short:

  1. Multipass fogging is as easy as setting the fog color to black after the first normal pass.
  2. Multipass alpha blending is done in essentially the same way, just do the first pass normally, and then do the subsequent passes with src blend factor set to GL_SRC_ALPHA and dst blend factor set to GL_ONE.
  3. Both techniques can be combined together.

So after all multipass lighting is not so difficult to program! Actually it is easier than trying to add all the lights affecting an object to the same shader. The latter aproach may be more optimized but it’s very troublesome to manage (or generate?) the combinational explosion of shaders having 1, 2 ,3 or more lights where each of the lights can be of any type.

PS: You can use these techniques even with what I call “semi-multipass” lighting, when 2 or more lights are rendered at the same pass. If you don’t believe me, just do the math :)


Handling fog with multipass lighting

July 15, 2009

The thought of fog problems with multipass lighting has prevented me from using multipass in my renderers for too long. Today I decided to stop and think about it for a minute, and the solution is very simple!

Fog is implemented as a linear interpolation between the shaded color of the pixel and the fog color, using a fog factor. In my case the fog factor is calculated like this:

float fog_factor = exp2(-abs(view_dist * fog density));

and here is the cg code for calculating the final color:

final_color = lerp(fog_color, final_color, fog_factor);

which is equivalent to:

final_color = fog_color * (1.0 – fog_factor) + final_color * fog_factor;

when multipass lighting is used, fog should be applied to the final color (the sum of all passes):

final_color = fog_color * (1.0 – fog_factor) + (pass1_color + … + passn_color) * fog_factor;

or:

final_color = fog_color * (1.0 – fog_factor) + pass1_color * fog_factor + … + passn_color * fog_factor;

or:

final_color = lerp(fog_color, pass1_color, fog_factor) + pass2_color * fog_factor + … + passn_color * fog_factor;

So the first pass is rendered normally, as in single pass rendering, but the next passes are drawn without adding “fog_color * (1.0 – fog_factor)” each time. We have two options to implement this:

  • Write separate shaders for the first and for the subsequent passes, or
  • Make the term “fog_color * (1.0 – fog_factor)” be equal to zero in all passes except the first.

The first option might seem a little more oprimized at first, because it eliminates one or two instructions, but in fact it isn’t. The cg manual states that it’s better to use standard library functions, like “lerp”, than coding them on your own, because they are more optimized.

On top of being more optimized, the second option is also more convenient, because you don’t need to maintain separate shaders with no fog term. You just zero the fog term :)

So how can we zero the fog term? If you are reading this I think I don’t need to tell you (in fact I shouldn’t have even argued about why the second approach is better …).

Ok, ok ….

Short answer: Do the first pass normally, and the rest of the passes with fog enabled and set the fog color to black.


Follow

Get every new post delivered to your Inbox.