Issues with translucency (blending)

Hi.

I’ve followed the advice from:

https://home.comcast.net/~tom_forsyth/blog.wiki.html#[[Premultiplied%20alpha]]

edit: sorry, forum software breaks link - it really does have square brackets in the URI

I’ve developed a renderer that uses premultiplied alpha throughout (all textures are
premultiplied, and the output of the renderer itself is a premultiplied alpha image in
a framebuffer). The renderer works by first clearing the framebuffer to (0, 0, 0, 0), then
rendering all opaque objects in any order (using the depth buffer to prevent overdraw),
and then rendering all translucent objects from furthest to nearest. I don’t think there’s
anything controversial there other than maybe that it produces an image that has
translucent regions, because the result is intended for use in further compositing down
the line, whereas I imagine most renderers produce a completely opaque image (and
so typically clear to (0,0,0,1) or some other opaque background color).

I’m having issues when it comes to rendering translucent objects.

The renderer uses standard and unsurprising multipass lighting. Here’s an image of
an opaque object rendered with five lights (one large white light is offscreen):

This is rendered in the usual style: The square in the middle is rendered five times,
once with each light, with the contributions from each light being summed with additive blending.
Basically:


glBlendFunc(GL_ONE, GL_ONE);

for (o : objects) {
  for (l : lights) {
    render(o, l);
  }
}

However, and this is taken directly from the blog post above, for translucent
lighting, one needs to set a different blending mode for the first light, but then
switch back to additive blending for the rest. So, essentially:


for (o : objects) {

  boolean first = true;
  for (l : lights) {
    if (first) {
      glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    } else {
      glBlendFunc(GL_ONE, GL_ONE);
    }

    render(o, l);
    first = false;
  }
}

This does appear to give roughly the right results, except that for some
reason, I have to perform premultiplication on the actual resulting
fragment color produced by the shader. That is, I have to do:


  out_rgba = vec4(lit.xyz * albedo.w, albedo.w);

… where out_rgba is my fragment shader’s output, lit is the
final color of the surface with lighting applied, and albedo is the
surface albedo (either sampled from a premultipled texture, or specified
as a premultiplied color via a uniform, etc). Otherwise, the shaders used
for translucent objects are character-for-character identical to their opaque
equivalents (the shaders are generated, and differ only in the final line that
performs the premultiplication).

Here’s what happens when rendering the above object as a translucent without
performing the premultiplication in the shader (note the excessive brightness):

… and here’s what happens when I do perform the premultiplication
in the shader:

… which is more like what I’d expect to see and seems almost pixel identical
other than the fact that the object is (deliberately) slightly translucent.

My question is: Why do I have to perform this extra step? It seems silly to have to
have “translucent” variants of all of the existing shaders when they’re otherwise
identical except for the final line. Is it possible to get the blending hardware to do
this step for me?

To clarify a little, the current opaque shaders produce a fragment color with:


  out_rgba = vec4(lit.xyz, 1.0);

… and the translucent shaders do exactly the same for the rest of the code
except they produce a final fragment color with:


  out_rgba = vec4(lit.xyz * albedo.w, albedo.w);

In an ideal world, I’d run the same shader in both the opaque and translucent
cases and produce a fragment color with:


  out_rgba = vec4 (lit.xyz, albedo.w);

… and the blending hardware would be configured such that:

[ol]
[li] I get simple
[/li]additive blending in the opaque case, with the value in the alpha channel
of the fragment set to 1.0 so that light contributions are summed and
objects are fully opaque.
[li] I get the result that I already get by manually premultiplying in the translucent
[/li]case, but without having to do that multiplication myself.
[/ol]

… Is this even possible?

Maybe I’m not understanding the issue correctly, but it seems to me that you want GL_SRC_ALPHA as source blend factor, i.e glBlendFunc(GL_SRC_ALPHA, GL_ONE); or perhaps make use of glBlendFuncSeparate/glBlendEquationSeparate to have the alpha channel treated specially (since you want the image to be blended itself).

For the first light pass when rendering a translucent object?

I’ll give it a go.

Hm, no… That seems to make the resulting image light-order dependent (if you render lights A B C, you get a different result than if you’d rendered C B A). Because the renderer doesn’t (and can’t) guarantee the order of lights, it results in flickering.