Search code examples
openglbufferblendingfbo

Painting in buffer. Issue with alpha chanel


I try to implement something like a paint tool and stuck with a problem alpha chanel of brush. My brush is a .PNG texture with transparent background. I draw in the RGBA buffer. My code:

initFBO(...)
...

glEnable(GL_BLEND);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);

glBindFramebuffer(GL_FRAMEBUFFER, FBO);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

drawBrush(...)

glBindFramebuffer(GL_FRAMEBUFFER, 0); 

drawFBO(...)

It works but if I stop my brush, alpha channel from brush will be to accumulate in the buffer to replace color from.

enter image description here

I'm not familiar with blending in Opengl too close and have issue with blending parameters. Is my problem in the blending or something else?

Update:

initFBO(GLuint &framebuff, GLuint &text)
{
    glGenFramebuffers(1, &framebuff);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuff);
    glGenTextures(1, &text);

    glBindTexture(GL_TEXTURE_2D, text);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screenWidth, screenHeight, 0, GL_RGB, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, text, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

drawBrush(glm::vec2 mousePosition, GLuint texture, Shader shader)
{
    glm::mat4 model;
    model = glm::translate(model, glm::vec3(mousePosition, 1.0));

    GLfloat brushVertices[] =
    {
        -0.1f, 0.1f, 0.0f, 0.0f, 1.0f,
        -0.1f,-0.1f, 0.0f, 0.0f, 0.0f,
         0.1f, 0.1f, 0.0f, 1.0f, 1.0f,
         0.1f,-0.1f, 0.0f, 1.0f, 0.0f
    };

    glGenVertexArrays(1, &brushVAO);
    glGenBuffers(1, &brushVBO);
    glBindVertexArray(brushVAO);
    glBindBuffer(GL_ARRAY_BUFFER, brushVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(brushVertices), &brushVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);

    shader.set();
    glUniform1i(glGetUniformLocation(shader.Program, "texture"), 0);
    glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);

    glBindVertexArray(brushVAO);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);
}

drawFBO(GLuint texture, Shader shader)
{
    GLfloat screenquadVertices[] =
    {
    -1.0f, 1.0f, 0.0f,  0.0f, 1.0f,
    -1.0f, -1.0f,0.0f,  0.0f, 0.0f,
    1.0f, 1.0f, 0.0f,  1.0f, 1.0f,
    1.0f, -1.0f,  0.0f,  1.0f, 0.0f
    };

    glGenVertexArrays(1, &screenquadVAO);
    glGenBuffers(1, &screenquadVBO);
    glBindVertexArray(screenquadVAO);
    glBindBuffer(GL_ARRAY_BUFFER, screenquadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(screenquadVertices), &screenquadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glBindVertexArray(0);

    shader.set();
    glUniform1i(glGetUniformLocation(shader.Program, "texture"), 0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);

    glBindVertexArray(screenquadVAO);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindVertexArray(0);
}

Update2

Ok. I tried both your recomend but results are simmilar and don't explain my problem. Look at:

1) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); and two premultiplied .PNG texture - witha black and with white.

enter image description here

2) glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); and straight-alpha texture.

enter image description here


Solution

  • When blending into a texture with a straight alpha channel, the correct blending equations involve a division by the alpha values, and such equations cannot be expressed within the linear formulas that OpenGL (and GPUs) provide with the glBlendFunc and glBlendFuncSeparate.

    Instead, you should use premultiplied alpha. In principle you do that by calling

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    

    once, and ensuring that all your inputs and outputs are premultiplied alpha.

    However, standard PNGs are in straight alpha (though non-standard PNGs with premultiplied alpha exist... probably not your case). To deal with it you can either premultiply the PNGs when you load them, or use the following when in drawBrush:

    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    

    That will get a straight alpha shader output and blend it into a premultiplied alpha framebuffer. Therefore, when rendering that framebuffer to the screen, remember to use the glBlendFunc for premultiplied alpha (the first one).