Hi everyone.
I have a follow-up issue, so I figure it would be more helpful to just continue this thread.
Previously, I was helped in setting up a framebuffer to render to texture. I got this to work great, for 100% opaque colors.
But, when working with opacities (alpha channel) less than 100%, the image’s pixels “decay” after every time I re-render it to texture (i.e. the RGB[A] components degrade towards 0).
Now, my question is, is there a blending option that lets the texture pixels get set without multiplying by the alpha, but otherwise still following standard blending glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); as you would expect?
In other words, if I render to texture using a renderbuffer, and then do:
glBindTexture(GL_TEXTURE_2D, texture_id);
glGetTexImage(GL_TEXTURE_2D,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
pixels.data());
saveImageToFile(filename, pixels, width, height);
I want the pixels of that texture to be their un-multiplied (NOT multiplied by alpha) values.
Example:
Let’s say I’m drawing to texture’s pixel in a blank background, RGBA(0, 0, 0, 0), to keep things simple. (Although I should be able to blend with any type of underlying RGBA pixel.)
If I load a PNG image with RGBA(0, 200, 255, 254) color, the first time this is rendered to texture, every RGBA element is multiplied by (254/255) and rounded, giving me this sequence every time I iterate the rendering to texture:
Original brush texture:
RGBA(0, 200, 255, 254)
Texture output after the i'th click (brush stroke) renders:
click 1: RGBA(0, 199, 254, 253)
0 = round( 0 * 254/255)
199 = round(200 * 254/255)
254 = round(255 * 254/255)
253 = round(254 * 254/255)
click 2: RGBA(0, 197, 252, 251)
0 = round( 0 * 253/255) # uses RGBA of
197 = round(199 * 253/255) # previous render,
252 = round(254 * 253/255) # including alpha.
251 = round(253 * 253/255) #
click 3: RGBA(0, 194, 248, 247)
0 = round( 0 * 251/255) # see the pattern?
194 = round(197 * 251/255)
248 = round(252 * 251/255)
247 = round(251 * 251/255)
click 4: RGBA(0, 188, 240, 239)
etc.
click 5: RGBA(0, 176, 225, 224)
click 6: RGBA(0, 155, 198, 197)
click 7: RGBA(0, 120, 153, 152)
click 8: RGBA(0, 72, 91, 91)
click 9: RGBA(0, 26, 32, 32)
click 10: RGBA(0, 3, 4, 4)
click 11: RGBA(0, 0, 0, 0)
After 10 iterations, it’s decayed so much that I no longer see the image. I want to stop this from happening at all.
I want the texture’s pixel to be obtained back as (0, 200, 255, 254) when given back to me after being rendered to texture.
Note that after the first click I make, that makes the original pixel get colored, I zero-out the brush texture to prevent more clicks from re-applying the color:
glBindTexture(GL_TEXTURE_2D, brush_textureID);
glTexSubImage2D(
GL_TEXTURE_2D,
0, // level (base image is 0)
0, // x offset (we're overwriting
0, // y offset every pixel)
diameter, // width
diameter, // height
GL_RGBA,
GL_UNSIGNED_BYTE,
blank.data()
);
Here is my function that renders to texture. I’ve tried using different values in glBlendFunc to no avail.
void MyGLCanvas::render_brush_to_frame()
{
glUseProgram(shader.getProgram());
// Set our "myTextureSampler" sampler to use Texture Unit 0
glUniform1i(uniformTextureID, 0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Render to texture instead
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 1st attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, brush_vertex_buffer_id);
glVertexAttribPointer(
0, // attribute 0. Matches layout of shader.
3, // size (X+Y+Z = 3)
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
// 2nd attribute buffer : UVs
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, uvbuffer_unflipped);
glVertexAttribPointer(
1, // attribute. No particular reason for 1, but must match the layout in the shader.
2, // size : U+V => 2
GL_FLOAT, // type
GL_FALSE, // normalized?
0, // stride
(void*)0 // array buffer offset
);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_uv_buffer_unflipped_data), g_uv_buffer_unflipped_data, GL_STATIC_DRAW);
// Draw underlying frame:
glBindTexture(GL_TEXTURE_2D, textureID);
glDrawArrays(GL_TRIANGLES, 0, 3 * 2);
// Draw brush to frame:
glBindTexture(GL_TEXTURE_2D, brush_textureID);
glDrawArrays(GL_TRIANGLES, 0, 3 * 2);
// Stop rendering to texture, continue rendering to main screen:
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
std::swap(textureID, renderedTexture);
}
If I change the Blend Func when rendering to texture to be:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE_MINUS_DST_ALPHA, GL_ONE);
it makes it degrade A LOT slower, but it still degrades:
0: RGBA(0, 199, 254, 254)
1: RGBA(0, 198, 253, 254)
2: RGBA(0, 197, 252, 254)
3: RGBA(0, 196, 251, 254)
4: RGBA(0, 195, 250, 254)
5: RGBA(0, 194, 249, 254)
6: RGBA(0, 193, 248, 254)
7: RGBA(0, 192, 247, 254)
8: RGBA(0, 191, 246, 254)
9: RGBA(0, 190, 245, 254)
10: RGBA(0, 189, 244, 254)
11: RGBA(0, 188, 243, 254)
…
121: RGBA(0, 127, 133, 254)
122: RGBA(0, 127, 132, 254)
123: RGBA(0, 127, 131, 254)
124: RGBA(0, 127, 130, 254)
125: RGBA(0, 127, 129, 254)
126: RGBA(0, 127, 128, 254)
127: RGBA(0, 127, 127, 254)
128: RGBA(0, 127, 127, 254) [steady state]
I can show more code if necessary… I’ve whittled down my code a lot to only show this behavior.