Search code examples
openglalphablendingblending

OpenGL Abnormal blending result


Mesh (vertexBuffer and indexBuffer):

Several 2D triangles overlapped with each other. The vertex color of every vertex is R:1, G:0, B:0, A:0.005(transparent red). Triangles are combined into one mesh.

Rendering code: (C# code, but it should be the same for another languages)

GL.Enable(GL.GL_BLEND);
GL.BlendEquation(GL.GL_FUNC_ADD_EXT);
GL.BlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
GL.Disable(GL.GL_CULL_FACE);
GL.Disable(GL.GL_DEPTH_TEST);
GL.Disable(GL.GL_ALPHA_TEST);
GL.DepthFunc(GL.GL_NEVER);
GL.Enable(GL.GL_SCISSOR_TEST);

GL.ClearColor(1, 1, 1, 1);
GL.Clear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
GL.Viewport(0, 0, width, height);
GLM.mat4 ortho_projection = GLM.glm.ortho(0.0f, width, height, 0.0f, -5.0f, 5.0f);

material.program.Bind();
material.program.SetUniformMatrix4("ProjMtx", ortho_projection.to_array());

GL.BindVertexArray(material.vaoHandle);
GL.BindBuffer(GL.GL_ARRAY_BUFFER, material.VboHandle);
GL.BufferData(GL.GL_ARRAY_BUFFER, vertexBuffer.Count*sizeof_Vertex, vertexBuffer.Pointer, GL.GL_STREAM_DRAW);
GL.BindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, material.elementsHandle);
GL.BufferData(GL.GL_ELEMENT_ARRAY_BUFFER, indexBuffer.Count *sizeof_Index, indexBuffer.Pointer, GL.GL_STREAM_DRAW);

GL.DrawElements(GL.GL_TRIANGLES, elemCount, GL.GL_UNSIGNED_INT, IntPtr.Zero);

vertex shader

#version 330
uniform mat4 ProjMtx;
in vec2 Position;
in vec2 UV;
in vec4 Color;
out vec2 Frag_UV;
out vec4 Frag_Color;
void main()
{
    Frag_UV = UV;
    Frag_Color = Color;
    gl_Position = ProjMtx * vec4(Position.xy,0,1);
}

fragment shader

#version 330
in vec2 Frag_UV;
in vec4 Frag_Color;
out vec4 Out_Color;
void main()
{
    Out_Color = Frag_Color;
}

I thought the overlapped triangles would be rendered like this:

overlapped overlapped(wireframe)

But the actual rendering result is:

result

After each drawing and SwapBuffer call, the red value of the rendered polygon becomes more and more stronger.

Why? Shouldn't those transparent triangles be blended only once? I mean, as the time goes by, the rendered result should be static, not animated.

Please note that I Want overlapped pixels be rendered in a stronger red color. But I Don't Want the whole mesh(overlapped triangles) be rendered in a more red color frame by frame. Since the current bound frame buffer is cleared, then the blending works correctly, the rendered result shouldn't be more and more red IMO.


Solution

  • You have disabled GL_DEPTH_TEST, you have no stencil test and you have no other operation specified which prevents a fragment to be drawn.

    The blend function is a function of the target color and the source color. Every time a fragment is draw the color in the target buffer changes.

    If you set the glBlendFunc with the parameters (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) then the destination color is calculated as follows:

    C_dest = C_src * A_src + C_dest * (1-A_src)
    

    If you blend C_dest = 0 with C_src = 1.0 and A_src = 0.05 then:

    C_dest = 0.05 = 1.0 * 0.05 + 0.0 * 0.95 
    

    If you repeat blending the same color C_src = 1.0 and A_src = 0.05 then the destination color is brightened:

    C_dest = 0.0975 = 1.0 * 0.05 + 0.05 * 0.95
    

    Repeating this makes the target color brighter and brighter.

    To prevent this you can use the depth test. For example, with the depth function GL_LESS, which prevent that a fragment is draw, if its depth is equal or even higher than the depth stored in the depth buffer:

    GL.Enable(GL.GL_DEPTH_TEST);
    GL.DepthFunc(GL.GL_LESS);
    

    Or you can set up a stencil buffer and a stencil test. For example, the stencil test can be set to pass only when the stencil buffer is equal to 0. Every time a fragment is to be written the stencil buffer is incremented. If the same fragment is to be written a second time, then the test fails:

    GL.Clear( GL.GL_STENCIL_BUFFER_BIT );
    GL.Enable( GL.GL_STENCIL_TEST );
    GL.StencilOp( GL.GL_KEEP, GL.GL_KEEP, GL.GL_INCR );
    GL.StencilFunc( GL.GL_EQUAL, 0, 255 );
    

    Extension of the answer

    Note, GL.Clear(GL.GL_COLOR_BUFFER_BIT); does clear the color plane of the draw buffer immediately, of course you have to do it before every refresh of the scene and it does effect the currently bound frame buffer only.

    Further the color mask for frame buffer writing operations has to be proper set.

    GL.ColorMask(GL.GL_TRUE, GL.GL_TRUE, GL.GL_TRUE, GL.GL_TRUE);
    

    If you use double buffer, ensure that the buffers are swapped only once per refresh, not twice.

    Note, you have to be careful that the geometry is not overlapping and is draw only once!