0

My scene is composed of layers that render to framebuffers. Within the framebuffers, elements are drawn back to front and are blended with ColorWithAlpha or EraseWithAlpha. Framebuffers are blended with the corresponding *WithPremultipliedAlpha versions. The following code controls blending:

switch (m_blendMode)
{
case BlendMode::ColorWithAlpha:
{
    glBlendEquation(GL_FUNC_ADD);
    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    break;
}
case BlendMode::ColorWithPremultipliedSrcAlpha:
{
    glBlendEquation(GL_FUNC_ADD);
    glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    break;
}
case BlendMode::EraseWithAlpha:
{
    glBlendEquation(GL_FUNC_ADD);
    glBlendFuncSeparate(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
    break;
}
case BlendMode::EraseWithPremultipliedSrcAlpha:
{
    glBlendEquation(GL_FUNC_ADD);
    glBlendFuncSeparate(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
    break;
}

I'm struggling to get erasing working across frame buffer boundaries. Drawing all elements back to front into one framebuffer using ColorWithAlpha is the equivalent of drawing all elements except one into one framebuffer and then drawing the final layer into a separate framebuffer using ColorWithAlpha, and then blending the separate framebuffer into the original using ColorWithPremultipliedAlpha. The same is not true for erasing. I've tried drawing layers back to front using ColorWithAlpha and then EraseWithPremultipliedAlpha to blend the target FBO with the dest FBO. This is unfortunately not equivalent to drawing all elements in the top FBO directly into the dest FBO with EraseWithAlpha. Is it possible to make the erase blend mode associative, enabling the same setup as exists with the coloring modes? If not, is there a pseudo erase blend mode that is associative and could be swapped in here?

user1832287
  • 329
  • 3
  • 11

1 Answers1

0

The math is correct. It turned out to be caused by quantization artifacts in the alpha channel. Floating point truncation reduces the accumulated alpha when layers intended to erase are drawn back to front using ColorWithAlpha into an intermediate framebuffer. The resulting alpha is

destAlpha * (1 - (a[i] + ((1 - a[i])a[i-1]) ...)

where i goes from 0 to the number of layers - 1. When drawn back to front using EraseWithAlpha, the result alpha is

destAlpha[i] * (1 - a[i])

The only difference between these two recurrences is association. The dest alpha is multiplied into the combined alpha for all layers after the initial draw for the case where each erase layer is drawn sequentially into the target. For the case with the framebuffer, the dest alpha is only accounted for at the end, resulting in different floating point truncation. Please comment if there is a clever way to make the floating point error accumulate the same way for these two use cases. For now I have solved it by making sure that layers requiring redraw are grouped in the same way as they were for the original draw.

user1832287
  • 329
  • 3
  • 11