Problem with grey outline using transparent texture with only white contents

Hi all!

I’m trying to generate some white text/icons/shapes that I will use as a GUI overlay on my app.

The problem is no matter what I do there is some kind of problem that creates some sort of grey outline in the borders:

I’ve put together this minimal demo, there is a white text generated in a CanvasTexture, over a white plane so the text should be invisible, instead you will see unwanted grey outline:

This not only happens with CanvasTexture, also when loading any image, which is also a problem for me as I try to use an atlas of white icons.

I’ve tried a ton of things, turning on premultiplying of alpha (only worsens it), playing with the blendmode of the material, trying to fill the canvas with fully transparent white (‘#ffffff00’) instead of fully transparent black (‘#00000000’), change the type of the texture to more precision (HalfFloatType, FloatType), doubling the size of the texture, etc… Nothing worked…

I need it to work maintaining the normal opacity of the material, and without changing the default minFilter and magFilter as otherwise when I animate them they look horrible as the texture flickers constantly.

I’ve seen this problem already in the forum by some other users, even mentioned in the repo, but never solved, so maybe I’m more lucky now :slight_smile:

Thx!

Pixel data from a Canvas has premultiplied alpha, i.e. the RGB color is already multiplied by the alpha component. This is different from normal textures. It results in white pixels becoming gray, and normal alpha blending will lead to wrong blending results.

AFAIK, the only way to correct the error is to change the blending equation of the material:

    const whiteTextMaterial = new THREE.MeshBasicMaterial({
      map: canvasTexture,
      transparent: true,
      blending: THREE.CustomBlending,
      blendEquation: THREE.AddEquation,
      blendSrc: THREE.OneFactor,
      blendDst: THREE.OneMinusSrcAlphaFactor,
    });

This should be easier to do IMHO. There is a premultipliedAlpha boolean property on Material but what it actually does is beyond me.

1 Like

@grml Thx a lot for your solution, I think it helped me wrap my mind around it.

It’s similar to the one by WestLangley in the repo issue.

It has a big problem though that then the material opacity property stops working.

As you point out I was expecting premultipliedAlpha to do exactly that internally, but I don’t see any change setting it to true. Checking the source code I found out that, in theory, when you set it to true it will run this:

// With premultiplyAlpha
gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );

// Without premultiplyAlpha
gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );

Seems very similar to this overriding of the blending settings of the material manually, but I wonder why I see no difference. Maybe some of the experts on color space and alpha blending like @donmccurdy can shed some light about it? Maybe it’s a bug in the engine (that part of the code is pretty old)?

One VERY DIRTY hack I found:

If I set the canvas background to ALMOST white transparent pixels then the problem completely goes away without the need of any modification to the blending functions or having to set anything as premultipliedAlpha.

By trial and error I found the alpha can’t be just 0, has to be AT LEAST 0.002, probably because the browser (chrome in my case) otherwise just discards the operation, or maybe it’s a precision issue, I have no idea but it works:

ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "rgba(255, 255, 255, 0.002)";
ctx.fillRect(0, 0, width, height);

I don’t think this solution is very future proof, so still just a dirty hack, but someone may find it useful.

If the texture needed to be displayed comes from a server or user input we would still be screwed, so I would love someone to come with a better/real solution.

1 Like

Sounds like the “bleeding due to filtering” problem discussed here:

If you have the option of pre-processing these images it should be possible to fill the transparent pixels with the appropriate color — even if they’re fully transparent — but I’m not sure if the browser’s canvas-to-image APIs can do that.

1 Like

Write the character data only to the alpha channel, and fill the entire rgb channel of the buffer with white.