Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alpha Edge Quality of Canvas-based Textures #1864

Closed
scottgarner opened this issue May 3, 2012 · 18 comments
Closed

Alpha Edge Quality of Canvas-based Textures #1864

scottgarner opened this issue May 3, 2012 · 18 comments
Labels

Comments

@scottgarner
Copy link
Contributor

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!

@alteredq
Copy link
Contributor

alteredq commented May 4, 2012

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

@mrdoob
Copy link
Owner

mrdoob commented May 4, 2012

Maybe is the same as #1625?

@scottgarner
Copy link
Contributor Author

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?

@mrdoob
Copy link
Owner

mrdoob commented May 4, 2012

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:

  1. Create a new canvas.
  2. Do a fillRect() of full white.
  3. Do the drawImage() of the text (or however you're adding the text).
  4. Set context.globalCompositeMode to 'destination-in'. (cheat sheet)
  5. Do the drawImage() of the bubble image.

@mrdoob
Copy link
Owner

mrdoob commented May 4, 2012

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.

@scottgarner
Copy link
Contributor Author

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.

@mrdoob
Copy link
Owner

mrdoob commented May 4, 2012

Ah! Shame... Did you tried changing the pixels with imagedata.data? It's basically doing what you're doing in the shader.

@alteredq
Copy link
Contributor

alteredq commented May 4, 2012

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.

@scottgarner
Copy link
Contributor Author

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.

@scottgarner
Copy link
Contributor Author

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!

@alteredq
Copy link
Contributor

alteredq commented May 4, 2012

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.

@mrdoob mrdoob closed this as completed May 15, 2012
@maartenbreddels
Copy link

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)

@mrdoob
Copy link
Owner

mrdoob commented Aug 6, 2018

Oh! Interesting...
@Mugen87 would you like to take a look?

@Mugen87
Copy link
Collaborator

Mugen87 commented Aug 7, 2018

I'll try to make a fiddle so we can see the difference in a live example.

@maartenbreddels
Copy link

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.

@Mugen87
Copy link
Collaborator

Mugen87 commented Aug 7, 2018

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?

@maartenbreddels
Copy link

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.

@Mugen87
Copy link
Collaborator

Mugen87 commented Aug 7, 2018

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants