I'm seeing this in a few places, but when I create an image on a canvas and use it as a texture, I get black artifacts at the edges of the alpha. This doesn't happen when I use normal images as textures.
I've made the sky white in this example so you can really see the dark edges around the speech bubble:
http://tweetopia.net/r10/test/edgeTest.png
Is there something I can do to avoid this? I don't see these edges with I create and download a png of the canvas.
Thanks!
I think this may be caused by black RGB part of RGBA for pixels with alpha < 1.
You can try setting these semi and fully transparent pixels to white.
Or you may try to experiment with alpha premultiplication for texture / different material blending options:
http://mrdoob.github.com/three.js/examples/webgl_materials_blending_custom.html
Maybe is the same as #1625?
Hmm. Definitely seems related to that issue, mrdoob.
I'm not sure I can use your solution of adding kind of buffer pixels, though, since in some cases I'm using drawImage to add a png directly (as with the speech bubble).
alteredq, I checked out the blending example and did some experiments.
premultiplyAlpha seems to have no effect and I get really crazy effects when I use THREE.CustomBlending. I can see through the alpha to the web page body behind the canvas:
http://tweetopia.net/r10/test/blendTest.png
I'm probably doing something wrong. Here is my code:
var panelTexture = new THREE.Texture( $(tweetCanvas)[0] );
panelTexture.premultiplyAlpha = false;
panelTexture.needsUpdate = true;
var panelMaterial = new THREE.MeshBasicMaterial( { map: panelTexture } );
panelMaterial.transparent = true;
panelMaterial.doubleSided = true;
panelMaterial.blending = THREE.CustomBlending;
panelMaterial.blendSrc = THREE.SrcAlphaFactor;
panelMaterial.blendDst = THREE.SrcColorFactor;
panelMaterial.blendEquation = THREE.AddEquation;
var panelMesh = new THREE.Mesh( panelGeometry, panelMaterial);
It seems like one of those blending options may help if I can get it right, but the problem seems deeper. You can see the original bubble image here:
http://tweetopia.net/r10/textures/bubble.png
If I use that directly as a texture with loadImage, there are no artifacts.
And here is the bubble image drawn to the canvas and then saved from there as a png with toDataURL:
http://tweetopia.net/r10/test/bubbleTest.png
There's no weird alpha channel or anything as far as I can tell. So it seems like it must be something about the way the texture is created from the canvas?
bubble.png is fine. If I remove the alpha channel I get a white image.
However, bubbleTest.png shows the issue I'm talking about. If I remove the alpha channel in gimp I get this:
http://twitpic.com/9h00hs/full
Try doing this:
fillRect() of full white.drawImage() of the text (or however you're adding the text).context.globalCompositeMode to 'destination-in'. (cheat sheet)drawImage() of the bubble image.By default the pixels of a canvas are 0,0,0,0 and, in your case, you need them to be 255,255,255,0. Either you do it by hand modifying the imagedata.data, or you do it with the approach I posted above.
Ah, you are absolutely right about the default value and your approach to get white alpha totally makes sense, but unfortunately it doesn't seem to work for me. Even with "destination-in", which crops as expected, I still get black values in the alpha:
http://tweetopia.net/r10/test/bubbleTestWhite.png
I found a handy way to check with ImageMagick:
convert bubbleTestWhite.png -crop 1x1+0+0 txt:-
I also double checked with:
console.log(tweetContext.getImageData(0, 0, 1, 1));
tweetContext.globalCompositeOperation = "destination-in";
tweetContext.drawImage(tweetopia.bubbleImage,0,0);
console.log(tweetContext.getImageData(0, 0, 1, 1));
And got:
ImageData
data: CanvasPixelArray
0: 255
1: 255
2: 255
3: 255
length: 4
Then:
ImageData
data: CanvasPixelArray
0: 0
1: 0
2: 0
3: 0
length: 4
It looks like no matter what you do, the canvas ends up with black alpha. I even tried filling with various opacities of white, but as soon as I hit zero alpha, everything went black. It's like for the canvas, if alpha is zero, so are RGB.
As a solve for this particular case, I've made a shader that sets anything with alpha < 1.0 to white and that seems to work, but it would be problematic for something that was another color. I think for that stuff I'll just build everything with materials and geometry instead of using the canvas, I just didn't want to have to mess with text that way.
As always, you're incredibly awesome for taking so much time to help people with these issues.
Ah! Shame... Did you tried changing the pixels with imagedata.data? It's basically doing what you're doing in the shader.
Hmmm, so it seems like canvas always premultiplies alpha.
Did you try setting premultiplyAlpha to true? False is default so snippet you posted actually did no change.
Also did you already try to use material.alphaTest?
And I still think that there may be some blending combination that may work - try to make a copy of the blending example I posted, replacing one of the foreground images with yours canvas generated one and see how it looks against various backgrounds with different options.
Same results with getImageData, changing pixels and then putImageData.
premultiplyAlpha seemed to have no effect and material.alphaTest dropped some of the black, but made things more jagged.
I did some more experimenting with webgl_materials_blending_custom.html by dropping in my own image, and it actually looks like both One/SrcColor DstAlpha/SrcColor lose the black border, which is great, but for some reason I'm having a hard time porting the effect to my scene.
When I use:
bubbleMaterial.blending = THREE.CustomBlending;
bubbleMaterial.blendSrc = THREE.OneFactor;
bubbleMaterial.blendDst = THREE.SrcColorFactor;
bubbleMaterial.blendEquation = THREE.AddEquation;
I get:
http://tweetopia.net/r10/test/blendTest.png
(Those stripes are on the body, not in the render).
I'm probably missing something, but I don't know what.
Oh! I think I was confused by the labels in the example and had blendSrc and blendDst reversed.
It works now as expected as far as the edges are concerned, but the text colors get blown out with both THREE.OneFactor and THREE.DstAlphaFactor.
Looks like my best bet is the shader solution that makes alpha white. Thanks again for all of the help!
I managed to get quite ok looking results by turning on alpha premultiplication and just using custom blending, leaving everything else on default (SrcAlphaFactor + OneMinusSrcAlphaFactor):
texture.premultiplyAlpha = true;
bubbleMaterial.blending = THREE.CustomBlending;
http://alteredqualia.com/tmp/tests/blending/webgl_canvas_alpha_test.html
This btw differs from default NormalBlending which uses separate blending functions.
I had similar issues and this is key to working with a canvas texture I think:
texture.premultiplyAlpha = true;
I think it would make for a good default for CanvasTexture, since it is always pre-multiplied colors.
PS: Using blendSrc=THREE.SrcAlphaFactor kind of hides the issues (since it multiplies it back to straight colors), but using blendSrc=THREE.OneFactor really shows the issue (that's how I found out)
Oh! Interesting...
@Mugen87 would you like to take a look?
I'll try to make a fiddle so we can see the difference in a live example.
Would be great since it is difficult to isolate from my project and I'm no threejs expert. My guess is that edges will look much better by default (with premultiplyAlpha=true), and should look the same if blendSrc=THREE.OneFactor is used.
To get a clear overview of premultiplied and straight color if found https://limnu.com/webgl-blending-youre-probably-wrong/ a good resource to clear up the confusion I had.
I've created a little test case but I'm not sure the used sprite texture is good to demonstrate the issue:
https://jsfiddle.net/f2Lommf5/11045/
As far as I can see, setting premultiplyAlpha to true does not produce a different visual result.
@maartenbreddels Any tips to improve the demo?
Yes, I created a new version here:
https://jsfiddle.net/f2Lommf5/11064/
As you can see, the red circle (101 overlapped and blended) shows a dark edge (this is why i chased this). This is caused, because the colors will be 'un-multiplied', and later on in the opengl pipeline multiplied again (SrcAlphaFactor). For low alphas this will cause the artifacts (loss of precision I guess).
If you run the demo with premultiplyAlpha=true, and OneFactor, you will see a much better red circle.
So the defaults now (premultiplyAlpha=false, and SrcAlphaFactor) give a nice results most of the time, but I think it is wrong, and can lead to artifacts.
Thanks for the chances! The dark edges are now nicely to see^^
@mrdoob Still, changing the default values of normal blending (in this case the srcRGB parameter for blendFuncSeparate()) and CanvasTexture.premultiplyAlpha is a breaking change. How should we proceed with this issue?
Most helpful comment
I managed to get quite ok looking results by turning on alpha premultiplication and just using custom blending, leaving everything else on default (
SrcAlphaFactor+OneMinusSrcAlphaFactor):http://alteredqualia.com/tmp/tests/blending/webgl_canvas_alpha_test.html
This btw differs from default
NormalBlendingwhich uses separate blending functions.