1
\$\begingroup\$

I would like to implement dual depth peeling in my engine. I read the original paper and examined the corresponding source code. I understand the algorithm, except how the blending works, especially in the front-to-back part. I'm so confused now, I'm not sure anything about alpha blending.

So let's start with the basics. I think there are two options: either we compute the pixel's color and it's transparency and store them separately (straight), or we multiply them and then we don't use the alpha channel's value (premultiplied alpha). The second option is what we usually do when we set glBlendFunc's parameters to GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA. When we blend two pixels, the GPU multiplies the source color with the source alpha and it multiplies the destination color with (1 - source alpha). We don't multiply the destination color with the destination alpha because basically it's already multiplied with it. So we assume the destination alpha is 1 (and also the destination alpha is not correct, because of the GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA parameters, so we can't use it anyways). Am I right so far?

At this point the back-to-front part of the dual depth peeling makes sense to me. In dual_peeling_peel_fragment.glsl we just store the color with the alpha (we can't blend it yet because we have to use max blending in this shader).

void main(void)
{
    ...
    gl_FragData[2] = vec4(0.0);
    ...
    if (fragDepth == nearestDepth) {
        ...
    } else {
        gl_FragData[2] += color;
    }
}

Then in the dual_peeling_blend_fragment.glsl shader we just blend it with the background.

void main(void)
{
    gl_FragColor = textureRect(TempTex, gl_FragCoord.xy);
    ...
}

Here we use additive blending with the already mentioned parameters which makes sense to me.

But when we compute the front-to-back part in dual_peeling_peel_fragment.glsl, I don't understand the code.

void main(void)
{
    ...
    vec4 forwardTemp = textureRect(FrontBlenderTex, gl_FragCoord.xy);
    ...
    gl_FragData[1] = forwardTemp;
    ...
    float alphaMultiplier = 1.0 - forwardTemp.w;
    ...
    vec4 color = ShadeFragment();
    ...
    if (fragDepth == nearestDepth) {
        gl_FragData[1].xyz += color.rgb * color.a * alphaMultiplier;
        gl_FragData[1].w = 1.0 - alphaMultiplier * (1.0 - color.a);
    } else {
        ...
    }
}

Here we compute the color value and multiply it with some alpha values and we also compute the alpha. I don't see where these equations come from. We don't modify the destination color (which is the nearer), but we multiply the source color with the source alpha and with (1 - destination alpha). And then we also compute the alpha value. So this not straight neither premultiplied. Also in the paper they use slightly different equations in page 6. paper equations

Where the paper multiplies with destination alpha the source code multiplies with (1 - destination alpha). I found a blog post where the author is also confused about that.

And then, at the end in dual_peeling_final_fragment.glsl we blend together the back-to-front part with the front-to-back part.

void main(void)
{
    vec4 frontColor = textureRect(FrontBlenderTex, gl_FragCoord.xy);
    vec3 backColor = textureRect(BackBlenderTex, gl_FragCoord.xy).rgb;
    float alphaMultiplier = 1.0 - frontColor.w;
    gl_FragColor.rgb = frontColor + backColor * alphaMultiplier;
}

But I don't understand why we only multiply the back part with (1 - front part alpha). In this part we use additive blending.

\$\endgroup\$
1
\$\begingroup\$

I think a figured it out. I was correct about back-to-front blending. The destination color stores the already blended layers in premultiplied format, we assume that the alpha is 1. We don't care about what is actually stored in the alpha channel and we don't use this value when we blend a new layer. We can think about this like alpha is the amount of light which is reflected from the surface, while (1 - alpha) is the amount of light which is passing through to the farther layers. So it makes sense that when we blend a new layer, we multiply the new layer with it's alpha and we multiply the farther, already blended layers with (1 - alpha).

Front-to-back blending is the same but on the opposite direction. We store the the already blended (nearer) layers in the destination and we blend the source under it. We store the blended color in premultiplied format, but we also store it's alpha value and that's what confused me. Why are we store the alpha value if the color is already multiplied with it? We don't store the alpha because of the already blended layers, but rather because we have to know this information if we want to blend a new layer under the destination. We have to multiply the source color with (1 - destination alpha) because this is the amount of light which is passing through the already blended layers (and we also have to multiply it with it's own alpha of course). And we also maintain the destination's alpha value because we just blended a new layer.

The equations in the paper and in the code are a little bit different, but both of them are correct. The code does exactly what I described above. According to the paper we don't store the real alpha value of the blended layers but rather (1 - alpha) of the blended layers. This is a little counter intuitive because we don't store the alpha value in the alpha channel however if we think about it we don't have to. Because in the equation we have to multiply the source color with (1 - destination alpha). So this is basically just a trick to compute a little less in the shader.

This book's page 185-190 helped me a lot.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.