2

I've simplified my problem to drawing a square to illustrate. I want to draw a texture, which fades the alpha channel from 0 to 255 and back to 0 vertically, so that it blends with the background. Right now it darkens the background, and I can't figure out why. This is using OpenGL ES on iOS 9.

First, I enable the blending that I think I want:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Then I fill the buffer with green:

glClearColor(0, 0.5, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);

I have this image that I'm using as a texture:

Texture

And I draw the following vertices ({x, y, z},{r, g, b, a},{tx, ty}) with GL_TRIANGLES:

{{0.4, 0.3, 0}, {1, 0, 0, 1}, {0, 0}},
{{0.6, 0.3, 0}, {1, 0, 0, 1}, {1, 0}},
{{0.4, 0.5, 0}, {1, 0, 0, 1}, {0, 1}},
{{0.6, 0.3, 0}, {1, 0, 0, 1}, {1, 0}},
{{0.4, 0.5, 0}, {1, 0, 0, 1}, {0, 1}},
{{0.6, 0.5, 0}, {1, 0, 0, 1}, {1, 1}},

I expect the red component of the vertices to modulate the texture, as this is my fragment shader:

varying lowp vec4 DestinationColor;

varying lowp vec2 TexCoordOut;
uniform sampler2D Texture;

void main(void) {
    gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut);
}

It does, but the result is not simply red over green. It gets dark at the edges, where I would expect just the green to be drawn:

Problem

The answer to the similar question here says I should be using glTexEnv(GL_BLEND). However, I think that's an OpenGL ES 1 API.

Anyone know how I can achieve the desired blending? I want the red to fade across the green. The tops and bottoms should be green, not darker. There should not be obvious horizontal edges.

EDIT: I'm adding more pictures illustrating my problem. Here are two pink dots in GIMP:

Pink dots in GIMP

When I move one on top of the other (in GIMP), they blend nicely. They also blend nicely with the green background:

Pink dots in GIMP, overlayed

When I recreate this in OpenGL ES, I get the darkening, which is especially visible where the pink dots are overlayed. I made one very narrow this time so you could see the effect better:

Pink dots in OpenGL ES

I tried both glBlendFunc and glBlendFuncSeparate, which resulted in the same effect. Note that the background alpha is 1.

Community
  • 1
  • 1
Kevin Wood
  • 143
  • 10
  • I'm a bit rusty on blend functions, but your screenshot looks like the result is akin to Photoshop's "multiply". I think you need a different blend function, but can't quite answer which one right now... – Nicolas Miari Oct 20 '15 at 04:34
  • There should be a "cheat sheet" of common blending functions somewhere, for reference... – Nicolas Miari Oct 20 '15 at 04:40
  • I arrived at this blend function because it's recommended as "normal." GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA is supposed to mean that color_blended = color_src * alpha_src + color_destination * (1 - alpha_src). This is why I'm confused. Suppose alpha is 0.1, the red is 1, and the green is 0.5, those edges should be 0.1(1, 0, 0) + (1-0.1)(0,0.5,0) = (0.1, 0.45, 0), right? If you open a color comparison site and compare #1A7300 (blended example) to #008000 (background), it's not that dark. – Kevin Wood Oct 20 '15 at 06:12
  • Yes, it's one of the functions you use when blending bitmaps (sprites), depending on if the bitmap has "premultiplied alpha" or not (although I can't remember which one this is). The other one I think uses GL_ONE in one of the two parameters (again, don't remember which). – Nicolas Miari Oct 20 '15 at 06:14
  • Remember that `source` is the incoming value (the one you are overlaying), and `destination` is the value already in the framebuffer: https://www.opengl.org/sdk/docs/man2/xhtml/glBlendFunc.xml – Nicolas Miari Oct 20 '15 at 06:16

2 Answers2

2

Your issue is most likely in displaying the alpha channel itself, not the color. Since you use a direct blend function it will set the alpha channel to a lower then 1.0 value which you then see as a premultiplied color values on display: RGB = RGB*A. You should use a blend function separate to handle the alpha channel separately:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE)

This will now blend the colors as you expected but keep the destination alpha value of 1.0 which you set at clear color.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • This is really useful information, and this looks like a much better way to blend. but I regret to say it had no effect for me. – Kevin Wood Oct 20 '15 at 20:07
  • 1
    Could it be your texture is loaded from image so that the color is alpha premultiplied? Basically the same thing happens when you convert the image to RGBA raw data and the color RGB gets multiplied with alpha producing a shaded result. Try checking some semitransparent pixel if the RGB part is the same or is it scaled (it needs to be the same) – Matic Oblak Oct 20 '15 at 20:20
  • Brilliant idea! It had not occurred to me that the texture itself may be to fault. I just looked, and I'm drawing the texture into memory using (iOS constant) kCGImageAlphaPremultipliedLast. I'd guess you've almost certainly found the root of my problem. I'm not getting any data at all with kCGImageAlphaLast, so I'm going to keep working on this. I'll be back soon to verify that this is indeed the solution. – Kevin Wood Oct 20 '15 at 21:21
  • I have confirmed that the problem is indeed the texture. I haven't solved it yet, but I'm going to mark you correct, because I can clearly see the issue in the debugger now. – Kevin Wood Oct 21 '15 at 00:17
  • It is strange you can not create with "alpha last". Maybe you should add this code with variations you tried, explaining where the issue lies. – Matic Oblak Oct 21 '15 at 06:56
  • I don't understand why or what, but this solved my issue in drawing circles on a point vertices. Before they always showed white colors where alpha should be 0, now they actually look like circles and can overlap one another). Thanks! – Kevin van den Hoek Nov 16 '21 at 12:32
0

If your device uses defaults such that pixel data is assumed to be premultipled, then you could be getting results like the ones you are seeing. What I would suggest is to convert your alpha channel image to simple grayscale and then save that as a PNG and verify that it is grayscale and not BGRA format. Then, load this 8 bit per pixel data into a texture in the normal way without having to worry about any premultiply issues (a grayscale does not contain and alpha channel). Next, do the premultiply step in your shader by reading the single byte from the grayscale testure and then multiply the 3 components of the existing DestinationColor by this linear alpha value. This premultiply step got my shaders working on iOS, for example.

MoDJ
  • 4,309
  • 2
  • 30
  • 65