Search code examples
shaderalphablendingopengl-es-3.0

How to use blending with transparent textures and transparent color?


I'm using OpenGL ES 3. I want to draw transparent images. Some pixels of the image may have transparency from 0% to 100%. I also want to be able to set an argb value to change the whole image's color and transparency.

I have ended up with the following solution.

Rendering:

GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);

GL.Uniform4(shader.UniformColor, color);
GL.BindTexture(TextureTarget.Texture2D, sprite.TextureId);
...

Fragment Shader:

void main()
{
    fragColor = texture(text, textureCoordinate) * color;
}

The Problem is now that when i set a transparent color, the blending is done wrong, and the image gets brighter. It doesn't seem to be blended with the background correctly. When I use BlendingFactorSrc.SrcAlpha the transparency is right but there is a dark border around the image.

Does anyone have a solution to this problem? I have researched a lot and the solution for the dark border was to use the blending source factor one. But as decribed above i get another problem with that.

Thanks for your help!

Edit: Here's an illustration of the problem: https://i.sstatic.net/LAw3K.png


Solution

  • There were 2 different Problems.

    1: Seems like it's necessary to process the image data before loading it into a texture. I came across "premultiplied alpha" which seems to be necessary in my case. You basically multiply each rgb value by the alpha value. I still don't know why it has to be that complicated and you can't set it directly in opengl:

    private void PremultiplyAlpha(byte[] data)
    {
        for (int i = 0; i < data.Length; i += 4)
        {
            int alpha = data[i + 3]; // A
            float factor = alpha > 0 ? 255f / alpha : 1f;
    
            data[i] = (byte)(data[i] * factor); // R
            data[i + 1] = (byte)(data[i + 1] * factor); // G
            data[i + 2] = (byte)(data[i + 2] * factor); // B
        }
    }
    
    …
    
     byte[] data = GetPixelArray(bitmap);
    
     PremultiplyAlpha(data);
    
     GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, OpenTK.Graphics.ES30.PixelFormat.Rgba, PixelType.UnsignedByte, data);
    

    2: There's still a border when the texture is rendered in a different scale and uses mipmap. I had to change the following setting from NearestMipmapLinear to Nearest:

    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMagFilter.Nearest);// (int)All.NearestMipmapLinear);
    

    And I can use the following blend function:

    GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);