Standard blend mode produces a white border

I have tried different blend functions in Cocos creator but none of them gave me the correct result. In Cocos studio and Unity the standard shader looks correct from the start but in Cocos creator the standard blend has a white glow at the edge of where the solid color goes to transparrent pixels.

White border glow

Which version of Creator are you using ?

I think it’s because the png file is automatically alpha premultiplied in WebGL, which should be disabled now.

Try to use SRC: cc.ONE and DST: cc.ONE_MINUS_SRC_ALPHA, if it’s correct, then it’s alpha premultiplied

Hi Pandamicro

I am using 1.0.2.

I tried ONE and ONE_MINUS_SRC_ALPHA from the drop down list but that did not solve it (made it worse).
Is it possible to use a custom blend function on the sprite somehow?

Not sure if it’s related to this: alpha-blending-not-working-as-expected

Can you upload somewhere your project so that I can investigate into it ?

I have uploaded a zip with the background and foreground png images used in the screenshot. It should only take a few seconds to put them into a new scene and add the sprite components. Foreground should render before Background :slight_smile:

  • Thank you for your help.

interface png files

I have confirmed it was because we disabled automatic premultiply alpha in Creator, if I activate it, it looks the same as other tools.

Temporarily, you can export an alpha premultiplied image with Texture Packer

I’m not sure about how should we treat this problem, I will discuss with other team member tomorrow.

@dabingnn

Thanks Pandamicro it works exporting from texturepacker with premultiplied alpha on.

I hope you will consider enabling the automatic premultiply alpha in Creator. It will definitely be easier if we can export our files directly from Photoshop instead of using texturepacker.

Hi, I found this topic on Unity forum:

http://answers.unity3d.com/questions/10302/messy-alpha-problem-white-around-edges.html

Basically it’s due to Photoshop not recognizing the color of transparent pixels, and made them all white. The problem with automatic premultiplied alpha (which is enabled in Cocos2d-x and JS, but disabled in Creator) is that we need to use special blend functions for them, and it’s not easy to manage the blend functions. If you never touch the blend function, it works good, but once you do it, you must know what’s the difference between alpha premultiplied texture and non premultiplied, and do the right choice depends on file format (auto premultiplied alpha is only activated for PNG)

Hi Pandamicro

Yes I have encountered this issue in Unity as well but it is very easy to fix using a TGA file with an alpha channel. This way you can set your own “Mask color”. But TGA format is not supported in Creator unfortunately.

It seems that using the blend func. “SRC: ONE - DST: ONE_MINUS_SRC_ALPHA” is not the best solution since they do not look correct on my iphone 5c (ios 9.3.1) and Ipad2 (ios 9.1). it looks like the blend mode is not fully supported on those devices.

I think the optimal solution would be to enable automatic premultiply alpha in Creator, so it works the same way as the other editors?

1 Like

I believe I figured out where this glow comes from and how to beat it. At least in my case - webdesktop GL build, and I don’t want to use premultiplied alpha because I want it to be compatible with canvas mode.

Solution:
Set 'alpha' : false in the engine in following piece of code:

        if (cc._renderType === game.RENDER_TYPE_WEBGL) {
            this._renderContext = cc._renderContext = cc.webglContext
             = cc.create3DContext(localCanvas, {
                'stencil': true,
                'antialias': !cc.sys.isMobile,
                'alpha': true
            });
        }

or add background: black; to game canvas css style. I’m not sure yet which one is better.

Explanation:
By default Cocos Creator uses webgl canvas with enabled transparency. Now we try to draw semi-transparent sprite over opaque background with (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) blendFunc. RGB values are calculated correctly, but alpha channel gets the following:
res.a = src.a * src.a + (1 - src.a) * dst.a
With dst.a == 1 and 0 < src.a < 1 we get transparent pixels by drawing transparent on top of opaque. And through them the underlying color bleeds into the canvas

I’m not sure if your solution is still viable in Creator 1.4.0. Tried it in an onLoad function and gave me a bunch of errors.

I am have a similar problem getting semi-transparent sprite rendering over opaque background as well. It works fine in Canvas mode, but under WebGL I’d have to use texture packer to spit out the semi-transparent images with premultiplied alpha set to true. (looked through engine code but doesn’t seem to be an easy way to do this through the api)

This seems to be the end of my problem until I realize that upon running cc.fadeTo on the parent node of these images, the images would not fade correctly because I have set their srcBlendFactor to ONE, so instead of disappearing gradually it would slowly fade to the node’s color.

My current solution is to use 2 versions of the image for the sprite:

  • normal, untouched .png for Canvas mode
  • .png/.plist generated by Texture Packer with premultiplied alpha for WebGL mode

Then I’d attach the following script to each such sprite that sets the spriteFrame to the respective .png according to cc._renderType.

cc.Class({
    extends: cc.Component,

    properties: {
        normalSprite: cc.SpriteFrame,
        preMulAlphaSprite: cc.SpriteFrame,
    },

    // use this for initialization
    onLoad: function() {
            var sprite = this.sprite = this.node.getComponent(cc.Sprite);

        if (cc._renderType === cc.game.RENDER_TYPE_WEBGL) {
            sprite.spriteFrame = this.preMulAlphaSprite;
            sprite.srcBlendFactor = cc.BlendFunc.BlendFactor.ONE;
            this.spriteMode = cc.game.RENDER_TYPE_WEBGL;
        }
        else {
            sprite.spriteFrame = this.normalSprite;
            this.spriteMode = cc.game.RENDER_TYPE_CANVAS;
        }
    },

    update: function(dt) {
        const opacity = this.node.getDisplayedOpacity();
        if (cc._renderType === cc.game.RENDER_TYPE_WEBGL) {
            if (opacity < 255 && this.spriteMode == cc.game.RENDER_TYPE_WEBGL) {
                this.sprite.spriteFrame = this.normalSprite;
                this.sprite.srcBlendFactor = cc.BlendFunc.BlendFactor.SRC_ALPHA;
                this.spriteMode = cc.game.RENDER_TYPE_CANVAS;
            }
            else if (opacity == 255 && this.spriteMode == cc.game.RENDER_TYPE_CANVAS) {
                this.sprite.spriteFrame = this.preMulAlphaSprite;
                this.sprite.srcBlendFactor = cc.BlendFunc.BlendFactor.ONE;
                this.spriteMode = cc.game.RENDER_TYPE_WEBGL;
            }
        }
    }
});

So I’m basically switching between the 2 sprites depending on the renderType and the displayedOpacity of the sprite.
it’s very adhoc and unwieldy to me but I cannot think of a more elegant solution. The results are not perfect either b/c an untouched semi-transparent .png does not display perfectly on top of an opague background and it is quite noticeable even while fading.

In summary, what should I do if I were running an simple fadeTo action on a sprite with a premultiplied alpha image that has a srcBlendFactor of ONE?

Hi!
Ouch, using 2 sets of sprites is gonna be painful on big projects :slight_smile:
Creator has almost everything for automatic alpha premultily upon loading a GL texture, there’s even a corresponding flag in cc.macro. But it doesn’t work, probably because that conflicts with explicit blend function properties of cc.Sprites. However, I see absolutely no point in having such properties exposed on per-sprite basis, so I’m using this, see if it works for you:

/**
 *  Premultipliying alpha upon loading texture in webgl would save all the troubles
 *	with transparent canvas artifacts and alpha bleeding, 
 *	but as of v1.3.1 automatic premultipliying is not yet implemented
 */

/**
 *  This file overrides effect of cc.Sprite serialized blendFunc properties.
 *  If you need to use specific srcBlendFactor or dstBlendFactor value,
 * 	you'll have to set the explicitly from your scripts
 */
cc.macro.AUTO_PREMULTIPLIED_ALPHA_FOR_PNG = 1;
cc.macro.OPTIMIZE_BLEND_FUNC_FOR_PREMULTIPLIED_ALPHA = cc.macro.AUTO_PREMULTIPLIED_ALPHA_FOR_PNG;

if(!CC_EDITOR) {
	if(cc._renderType === cc.game.RENDER_TYPE_WEBGL) {
		cc.Texture2D.prototype._handleLoadedTexturePrePatch = cc.Texture2D.prototype.handleLoadedTexture;
		cc.Texture2D.prototype.handleLoadedTexture = function(premultiplied) {
			cc.assert(! this._handleOnBind, 'Texture.handleLoadedTexture: ' +  this._url);
			premultiplied === undefined && (premultiplied = cc.macro.AUTO_PREMULTIPLIED_ALPHA_FOR_PNG);
			this._handleLoadedTexturePrePatch(premultiplied);
		}
	}

	cc.Sprite.prototype._onSpriteFrameLoaded = function (event) {
		var self = this;
		var sgNode = this._sgNode;
		sgNode.setSpriteFrame(self._spriteFrame);
		
		var texture = self._spriteFrame && self._spriteFrame.getTexture(); 
	// override blend mode when using premultiplied alpha texture
		if(texture && texture.hasPremultipliedAlpha()) {
			sgNode._opacityModifyRGB = true;
		} else {
			sgNode._opacityModifyRGB = false;
		}
		self._applyCapInset();
		self._applySpriteSize();
		if (self.enabledInHierarchy && !sgNode.isVisible()) {
			sgNode.setVisible(true);
		}
	}

	cc.Sprite.prototype._initSgNode = function () {
		this._applySpriteFrame(null);
		var sgNode = this._sgNode;

		// should keep the size of the sg node the same as entity,
		// otherwise setContentSize may not take effect
		sgNode.setContentSize(this.node.getContentSize(true));
		this._applySpriteSize();

		sgNode.setRenderingType(this._type);
		sgNode.setFillType(this._fillType);
		sgNode.setFillCenter(this._fillCenter);
		sgNode.setFillStart(this._fillStart);
		sgNode.setFillRange(this._fillRange);
		sgNode.enableTrimmedContentSize(this._isTrimmedMode);
		this._blendFunc.src = cc.macro.BLEND_SRC;
		this._blendFunc.dst = cc.macro.BLEND_DST;
		sgNode.setBlendFunc(this._blendFunc);
	}
}

Just add this as a separate .js file to your project, it will run once on game launch and patch some CC functions

2 Likes

First of all, I need to thank you for your answer. It totally solved my problem!

With that being said, I do have 2 concerns:

  1. I feel slightly uncomfortable applying premultiplied alpha to all my images. Mostly because I don’t fully understand how this “conflicts with explicit blend function properties of cc.Sprite”. In your _initSgNode the _blendFunc.src/dst are being overrided - does this mean this would always override whatever blend mode I have set in the sprite component within the editor? And hence I’d have to set them explicitly from my scripts?

  2. I think in both of our solutions they set the src blend factor to ONE under WebGL except we do it differently. (changing cc.Sprite.srcBlendFactor vs cc.Sprite._blendFunc.src) However your solution works in animations where the sprite’s opacity fades while mine doesn’t. Is it the blend factor or the fact that you’re setting _opacityModifyRGB to true?

EDIT: ok I tried setting _opacityModifyRGB = true in my solution and it got rid of my fade problem. I suppose this needs to be used in conjunction with alpha premultiply.

Thanks again! I’m gonna read the engine source some more to try to understand this stuff!

Glad to hear that)

  1. Yes, my _initSgNode discards whatever BlendFunc settings cc.Sprite had and sets default blend function upon cc.Sprite creation. You can then change it, but do you really need to?
    My point is that I don’t need to set blend function for every sprite, I just want sprites to be displayed correctly. I’ve never run into issue where I would need to set different blend function for a specific sprite.
    I probably should wrap it with if(cc.macro.AUTO_PREMULTIPLIED_ALPHA_FOR_PNG) though

  2. If I remember correctly, _opacityModifyRGB affects how cc.Node.color is used for drawing (through which stuff like cc.fadeOut is implemented). So it’s basically the same thing - premultiplying color channels by alpha before engine sends data to GPU

Hello. Apparently Cocos Creator 1.5 breaks your code so here is the fixed version for reference.

/**
 *  Premultipliying alpha upon loading texture in webgl would save all the troubles
 *	with transparent canvas artifacts and alpha bleeding,
 *	but as of v1.3.1 automatic premultipliying is not yet implemented
 */

/**
 *  This file overrides effect of cc.Sprite serialized blendFunc properties.
 *  If you need to use specific srcBlendFactor or dstBlendFactor value,
 * 	you'll have to set the explicitly from your scripts
 */
cc.macro.AUTO_PREMULTIPLIED_ALPHA_FOR_PNG = 1;
cc.macro.OPTIMIZE_BLEND_FUNC_FOR_PREMULTIPLIED_ALPHA = cc.macro.AUTO_PREMULTIPLIED_ALPHA_FOR_PNG;

if (!CC_EDITOR) {
  if (cc._renderType === cc.game.RENDER_TYPE_WEBGL) {
    cc.Texture2D.prototype._handleLoadedTexturePrePatch = cc.Texture2D.prototype.handleLoadedTexture;
    cc.Texture2D.prototype.handleLoadedTexture = function (premultiplied) {
      cc.assert(!this._handleOnBind, 'Texture.handleLoadedTexture: ' + this._url);
      premultiplied === undefined && (premultiplied = cc.macro.AUTO_PREMULTIPLIED_ALPHA_FOR_PNG);
      this._handleLoadedTexturePrePatch(premultiplied);
    }
  }

  cc.Sprite.prototype._onTextureLoaded = function (event) {
    var self = this;
    var sgNode = this._sgNode;
    sgNode.setSpriteFrame(self._spriteFrame);

    var texture = self._spriteFrame && self._spriteFrame.getTexture();
    // override blend mode when using premultiplied alpha texture
    if (texture && texture.hasPremultipliedAlpha()) {
      sgNode._opacityModifyRGB = true;
    } else {
      sgNode._opacityModifyRGB = false;
    }
    self._applySpriteSize();
    if (self.enabledInHierarchy && !sgNode.isVisible()) {
      sgNode.setVisible(true);
    }
  }

  cc.Sprite.prototype._initSgNode = function () {
    this._applySpriteFrame(null);
    var sgNode = this._sgNode;

    // should keep the size of the sg node the same as entity,
    // otherwise setContentSize may not take effect
    sgNode.setContentSize(this.node.getContentSize(true));
    this._applySpriteSize();

    sgNode.setRenderingType(this._type);
    sgNode.setFillType(this._fillType);
    sgNode.setFillCenter(this._fillCenter);
    sgNode.setFillStart(this._fillStart);
    sgNode.setFillRange(this._fillRange);
    sgNode.enableTrimmedContentSize(this._isTrimmedMode);
    this._blendFunc.src = cc.macro.BLEND_SRC;
    this._blendFunc.dst = cc.macro.BLEND_DST;
    sgNode.setBlendFunc(this._blendFunc);
  }
}
2 Likes