4

Opengl es2 on the iphone. Some objects are made with multiple layers of sprites with alphas.

Then I also have UI elements that are also composited together from various sprites that I fade in/out over top of everything else. I do the fading by adjusting the alpha in the shader.

My textures are PNG with alpha made in photoshop. I don't premultply them on purpose. I want them to be straight alpha, but in my app they're acting as if they're premultiplied where I can see a dark edge around a white sprite drawn over a white background.

If I set my blend mode to:

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

The elements composite nicely together, no weird edges. But when elements are fading out they POP at the end. They will start to fade but won't go all the way down to alpha zero. So at the end of the fadeout animation when I remove the elements they "pop off" cause they're not completely faded out.

If I switch my blend mode to:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

The elements fade up and down nicely. But any white on white element has a dark edge around it from what looks like alpha premultiplication. See the white circle drawn over top of the white box:

enter image description here

But the other blends in the scene look good to me. The other transparent objects blend nicely.

Another important note is that my shader handles opacity for elements and colorizing elements. For each thing that is drawn I multiply by an element color and the final alpha with an opacity value:

void main(void)
{
    gl_FragColor = texture2D(u_textureSampler,v_fragmentTexCoord0);
    gl_FragColor = vec4(gl_FragColor.r*u_baseColor.r,gl_FragColor.g*u_baseColor.g,gl_FragColor.b*u_baseColor.b,gl_FragColor.a*u_baseColor.a * u_opacity);
}

This allows me to take a white object in my sprite sheet and make it any color I want. Or darken objects by using a baseColor of grey.

Every time I think I understand these blend modes something like this comes up and I start doubting myself.

Is there some other blend mode combo that will have smooth edges on my sprites and also support alpha fading / blending in the shader?

I'm assuming the GL_SRC_ALPHA is needed to blend using alpha in the shader.

Or is my problem that I need to use something other than PSD to save my sprite sheet? Cause that would be almost impossible at this point.

UPDATE:

I think the answer might just be NO that there is no way to do what I want. The 2nd blend mode should be correct. But it's possible that it's double multiplying the RGB with the alpha somewhere, or that it's premultiplied in the source file. I even tried premultiplying the alpha myself in the shader above by adding:

gl_FragColor.rgb *= glFragColor.a;

But that just makes the fades look bad as things turn grey as they fade out. If I premultiply the alpha myself and use the other blend mode above, things appear about the same as in my original. They fade out without popping but you can still see the halo.

badweasel
  • 2,349
  • 1
  • 19
  • 31
  • Some questions that might help diagnose the problem: How are you saving the images, and are you sure they're not premultiplied there? How are you loading them into GL? (Using `GLKTextureLoader`?) What iOS version are you testing on, and do you see the problem on some versions but not others? – rickster Nov 05 '13 at 19:02
  • I'm using photoshop to save the images. For example, the same psd layer saved to a stand along png and applied as a UIImage has a clean background. I am not using GLK at all. I'm using another texture loader, but that's a good place to look I guess. iOS version is 6.x and 7.x. I need to check to see if it's happening on both. I've only been looking at my ios6 testing phone. It's a pretty subtle thing and so we're releasing with it. But it is one of those things that bugs the heck out of me. – badweasel Nov 05 '13 at 19:10
  • Oh also.. now that I think about it again.. it definitely has to do with gl blending in open gl. My app also does it's own blending where I turn disable blending and combine two textures using my own blend modes and I don't see it there. mediump float srcOpacity = textureColor2.a * u_opacity; gl_FragColor = vec4(mix(textureColor.rgb, textureColor2.rgb, srcOpacity),1.0); Doesn't show those artifacts. See that 1.0 at the end. There's no way to do that in glBlendFunc – badweasel Nov 05 '13 at 19:16
  • From my experimenting, glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) will use the alpha of the source as the dest alpha instead of 1.0, which I believe is why it is darkening those edges. I think I'm actually seeing the black background though that. So the rgb is probably correct, but the alpha is showing background through there which is making the edge look dark.. That's my theory. – badweasel Nov 05 '13 at 19:19

2 Answers2

6

Here's a great article on how to avoid dark fringes with straight alpha textures http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/

If you're using mip-maps, that might be why your straight alpha textures have dark fringes -- filtering a straight alpha image can cause that to happen, and the best solution is to pre-multiply the image before the mip-maps are created. There's a common hack fix as well described in that article, but seriously consider pre-multiplying instead.

Using straight alpha to create the textures is often necessary and preferred, but it's still better to pre-multiply them as part of a build step, or during the texture load than to keep them as straight-alpha in memory. I'm not sure about OpenGL ES, but I know WebGL lets you pre-multiply textures on the fly during load by using gl.pixelStorei with a gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL argument.

Another possibility is if you're compositing many elements, your straight alpha blending function is incorrect. In order to do a correct "over operator" with a straight alpha image, you need to use this blending function, maybe this is what you were looking for:

gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

The common straight alpha blending function you referred to (gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)) does not correctly handle the destination alpha channel, you have to use separate blend functions for color and alpha when blending a straight alpha image source, if you intend to composite many layers using an "over operator". (Think about how you probably don't want to interpolate the alpha channel, it should always end up more opaque than both the source & dest.) And take special care when you blend, because the result of the straight-alpha blend is a premultiplied image! So if you use the result later, you still have to be prepared to do premultiplied blending. For a longer explanation, I wrote about this here: https://limnu.com/webgl-blending-youre-probably-wrong/

The nice thing about using premultiplied images & blending is that you don't have to use separate blend funcs for color & alpha, and you automatically avoid a lot of these issues. You can & should create straight alpha textures, but then pre-multiply them before or during load and using premult blending (glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)) throughout your code.

David
  • 688
  • 7
  • 13
4

AFAIK, glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); is for premultiplied alpha, and it should work well if you use colors as is. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); is for straight alpha.

Many texture loading frameworks implicitly convert images into premultiplied-alpha format. This is because many of them are doing image re-drawing into a new image, and CGBitmapContext doesn't support straight-alpha (non-multiplied) image. Consequently, they will usually generate premultiplied-alpha image. So, please look in your texture loading code, and check whether it was converted into premultiplied format.

Also, Photoshop (of course Adobe's) implicitly erases color informations on fully transparent (alpha=0) pixels when exporting to PNG. If you use linear or other texture filtering, then GPU will sample over neighboring pixels, and colors in transparent pixels will affect pixels at edges. But Photoshop already erased the color information so they will have some random color values.

Theoretically, this color bleeding can be fixed by keeping correct color values on transparent pixels. Anyway, with Photoshop, we have no practical way to export a PNG file with keeping correct color value because Photoshop doesn't respect invisible stuffs. (it's required to write a dedicated PNG exporter Photoshop plug-in to export them correctly, I couldn't fine existing one which support this)

Premultiplied alpha is good enough just to display the image, but it won't work well if you do any shader color magics because colors are stored in integer form, so it usually doesn't have enough precision to restore original color value. If you need precise color magic stuffs, use straight alpha — avoid Photoshop.


Update

Here's my test result with @SlippD.Thompson's test code on iPhone simulator (64-bit/7.x)

 <Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 24 bits/pixel; 3-component color space; kCGImageAlphaNone; 2048 bytes/row.
    cgContext with CGImageAlphaInfo 0: (null)
    cgContext with CGImageAlphaInfo 1: <CGContext 0x1092301f0>
    cgContext with CGImageAlphaInfo 2: <CGContext 0x1092301f0>
 <Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 32 bits/pixel; 3-component color space; kCGImageAlphaLast; 2048 bytes/row.
    cgContext with CGImageAlphaInfo 3: (null)
 <Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 32 bits/pixel; 3-component color space; kCGImageAlphaFirst; 2048 bytes/row.
    cgContext with CGImageAlphaInfo 4: (null)
    cgContext with CGImageAlphaInfo 5: <CGContext 0x1092301f0>
    cgContext with CGImageAlphaInfo 6: <CGContext 0x1092301f0>
 <Error>: CGBitmapContextCreate: unsupported parameter combination: 8 integer bits/component; 24 bits/pixel; 0-component color space; kCGImageAlphaOnly; 2048 bytes/row.
    cgContext with CGImageAlphaInfo 7: (null)
Slipp D. Thompson
  • 33,165
  • 3
  • 43
  • 43
eonil
  • 83,476
  • 81
  • 317
  • 516
  • I'm pretty sure `CGBitmapContext` supports straight-alpha. `kCGImageAlphaLast` vs. `kCGImageAlphaPremultipliedLast` (as an arg to `CGBitmapContextCreateWithData()`) is evidence of this. – Slipp D. Thompson Apr 25 '14 at 14:59
  • @SlippD.Thompson At the last time when I checked it, `CGBitmapContextCreate` printed *unsupported…* message and didn't work with `kCGImageAlphaLast`. Does it work now? – eonil Apr 25 '14 at 21:04
  • Yeah, didn't know it would do that.  I'll double check. – Slipp D. Thompson Apr 26 '14 at 03:32
  • Threw together a quick test of what `CGBitmapContextCreate()` does with each defined `CGImageAlphaInfo` mode.  Looks like every single one works on iOS 5.0 (sim) & 6.1 (device). – Slipp D. Thompson Apr 29 '14 at 02:07
  • @SlippD.Thompson I think your code has hardcoded `kCGImageAlphaPremultipliedFirst` constant. Seems to be a bug. Or is this intentional…? – eonil Apr 29 '14 at 03:20
  • @SlippD.Thompson I attached test result with correct constant lists. None of straight alpha mode supported on simulator. – eonil Apr 29 '14 at 03:30
  • 1
    Yeah, I did did make that mistake. Thanks.  … The “parameter combination” message is interesting; I'm going to play with permuting over other setting combinations some more. – Slipp D. Thompson Apr 29 '14 at 05:01
  • I don't have time this week to get in to this discussion but I would like to discuss with you guys more. email me at michael at badweasel.com. It's been over 6 months since I was dealing with this so I'll have to look at what I figured out back then. – badweasel May 08 '14 at 22:10
  • @badweasel You can continue this discussion at anytime you want. Having public discussion will help more people. – eonil May 09 '14 at 05:13
  • Although its out of date for current versions of Photoshop there is a plugin for 2014/15 versions of photoshop called SuperPNG to deal with transparent pixels. – Jabokoe Mar 24 '20 at 11:51