1

I'm experimenting with the Cocos2d HTML5 framework but believe this is a standard WebGL problem I cannot pinpoint as I don't have good knowledge on the topic. If I have a standard PNG image with semi-transparent pixels such as soft edges, and if the background (canvas or other element behind RenderTexture) is filled with a non-black color, the sprite when drawn to the RenderTexture ends up soaking in the background even when it shouldn't. For instance, if the graphic I'm drawing is red and I use a green background, the translucent pixels eventually morph to yellow. See these pictures for illustration:

Appearance when against a solid black background, this is what I desire Appearance when against a solid black background, this is what I desire

Appearance against a non-black background, with the bg bleeding through Appearance against a non-black background, with the bg bleeding through

Whatever is in the background will show through when I place any non-100% opaque image.

The full JavaScript-based RenderTexture class code can be seen on the cocos2d-html5 Github Repository under cocos2d/misc_nodes/CCRenderTexture.js. It is a conversion from Cocos2d-x C++ / Objective C (which do not exhibit this problem). There was a reported issue #937 on cocosd-iphone issue tracker describing the same problem for the non-HTML version several years ago. It was since resolved but I can't seem to determine what the resolution was or how it could be translated to the WebGL format. I'm sure it's a blend issue, but I have tried changing the blend modes (from GL_ONE, GL_ONE_MINUS_SRC_ALPHA to GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA and others) to no avail.

Any help from OpenGL / WebGL experts would be much appreciated. If I use the standard "sprites" in Cocos2d outside of this RenderTexture the problem does not exist. Also, the MotionStreak functionality seems to work without fault too, but I require RenderTexture to create dynamic textures for further use.

gman
  • 100,619
  • 31
  • 269
  • 393
Sam J
  • 25
  • 2
  • 3

1 Answers1

2

WebGL defaults to the same as images and canvas 2D, namely it expects the contents of the canvas to be RGBA, it expects RGB to be premultiplied by the alpha (in other words if the alpha is zero the RGB has to also be zero since anything times zero equals zero). And finally blends with whatever is behind it with GL_ONE, GL_ONE_MINUS_SOURCE_ALPHA.

If you want want it different you have 2 options

  1. Tell WebGL not to have alpha

    You do this by creating a context with no alpha

    gl = canvas.getContext("experimental-webgl", { alpha: false });
    

    This is probably the best option for your case. Since there will be no alpha there's no blending required which is faster than blending with the background which is what the browser normally has to do.

  2. Tell WebGL your RGB values are not premultiplied

    gl = canvas.getContext("experimental-webgl", { premultipledAlpha: false });

    This means the browser will still blend your WebGL canvas with the background, it will just do it with GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA instead. You'll still see the green behind.

There's a few other solutions that might be useful in special cases

  1. If you need alpha in the destination for some calculations you can clear the alpha to 1 when you're done

    ...render scene...
    
    gl.colorMask(false, false, false, true);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    

    The blending will still be happening you just won't see it because all your alpha is set to 1. Not the best solution but if you need destination alpha it might be your only solution.

  2. If you don't want to see through to stuff behind the canvas then give the canvas a background color

    <canvas style="background-color: green;"></cavnas>
    

    Setting the canvas' background color to say black would also do it but that's also a waste because it would still be blending your canvas with black. If that's what you want then just turn the alpha off in the first place.

gman
  • 100,619
  • 31
  • 269
  • 393
  • Thank you for the detailed explanation and for giving me areas to investigate. I'm still not having luck, but figure it's due to something deeper in the Cocos2d engine. I do want a transparent context so that what is drawn to it can act as an overlay to other elements on the site such as canvas color. With alpha set to true in the context generation, a green body background, and clearColor(0,0,0,0) to the context, it seems the same issue persists (i.e., red texture drawn to context ends up looking yellow due to the green bg color). But I will continue investigating. – Sam J Oct 31 '13 at 18:15
  • You want to set alpha to `false` to not get the blending. The default is `true`. I'm a little confused. You say you want it to blend with the background. You have green background and you draw red. You see yellow. That's exactly what you'd get with blending. So what's the problem again? – gman Nov 01 '13 at 00:48
  • Sorry. I'm trying to establish a layered interface of sorts. So, I DO want transparency on the WebGL layer(s) so that what is behind it can show through, but I do NOT want what I draw on a given context/layer to be affected by what is behind it (i.e., the page's background). See [this illustration](http://i.imgur.com/CNSReXW.png) for instance; the rainbow background would correspond to the canvas or body background texture. The red/white/green lines would be what I want to draw to the GL layer without them being affected by the bg. Like Photoshop layers. In my tests (C2D) they are affected. – Sam J Nov 01 '13 at 03:50
  • I think what you want to do then either #2 above or manually premultiply the alpha. In the example pictures you have above you'd need need to use premultiplied alpha. If your textures are already premultiplied then you'd do nothing. Otherwise you could try `gl_FragColor = vec4(color.rgb * color.a, color.a);` for example. Unfortunately it's not that simple. To use premultiplied alpha in general you'd need to either (a) make all your data, shaders, and blending use premultiplied or else (b) render to a texture and then post process it into a premultiplied image. Option (2) will do that for you. – gman Nov 03 '13 at 16:39
  • Here's a fiddle showing premutliplied data and canvas settings http://jsfiddle.net/greggman/pWPPC/ – gman Nov 03 '13 at 16:59