diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/utils/mapPremultipliedBlendModes.js b/src/core/utils/mapPremultipliedBlendModes.js new file mode 100644 index 0000000..5122986 --- /dev/null +++ b/src/core/utils/mapPremultipliedBlendModes.js @@ -0,0 +1,38 @@ +import { BLEND_MODES } from '../const'; + +/** + * Corrects pixi blend, takes premultiplied alpha into account + * + * @memberof PIXI + * @function mapPremultipliedBlendModes + * @private + * @param {Array} [array] - The array to output into. + * @return {Array} Mapped modes. + */ + +export default function mapPremultipliedBlendModes() +{ + const pm = []; + const npm = []; + + for (let i = 0; i < 32; i++) + { + pm[i] = i; + npm[i] = i; + } + + pm[BLEND_MODES.NORMAL_NPM] = BLEND_MODES.NORMAL; + pm[BLEND_MODES.ADD_NPM] = BLEND_MODES.ADD; + pm[BLEND_MODES.SCREEN_NPM] = BLEND_MODES.SCREEN; + + npm[BLEND_MODES.NORMAL] = BLEND_MODES.NORMAL_NPM; + npm[BLEND_MODES.ADD] = BLEND_MODES.ADD_NPM; + npm[BLEND_MODES.SCREEN] = BLEND_MODES.SCREEN_NPM; + + const array = []; + + array.push(npm); + array.push(pm); + + return array; +} diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/utils/mapPremultipliedBlendModes.js b/src/core/utils/mapPremultipliedBlendModes.js new file mode 100644 index 0000000..5122986 --- /dev/null +++ b/src/core/utils/mapPremultipliedBlendModes.js @@ -0,0 +1,38 @@ +import { BLEND_MODES } from '../const'; + +/** + * Corrects pixi blend, takes premultiplied alpha into account + * + * @memberof PIXI + * @function mapPremultipliedBlendModes + * @private + * @param {Array} [array] - The array to output into. + * @return {Array} Mapped modes. + */ + +export default function mapPremultipliedBlendModes() +{ + const pm = []; + const npm = []; + + for (let i = 0; i < 32; i++) + { + pm[i] = i; + npm[i] = i; + } + + pm[BLEND_MODES.NORMAL_NPM] = BLEND_MODES.NORMAL; + pm[BLEND_MODES.ADD_NPM] = BLEND_MODES.ADD; + pm[BLEND_MODES.SCREEN_NPM] = BLEND_MODES.SCREEN; + + npm[BLEND_MODES.NORMAL] = BLEND_MODES.NORMAL_NPM; + npm[BLEND_MODES.ADD] = BLEND_MODES.ADD_NPM; + npm[BLEND_MODES.SCREEN] = BLEND_MODES.SCREEN_NPM; + + const array = []; + + array.push(npm); + array.push(pm); + + return array; +} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5734fc0..1225730 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -141,17 +140,13 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTint(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = renderer.bindTexture(tex); - renderer.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); } diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/utils/mapPremultipliedBlendModes.js b/src/core/utils/mapPremultipliedBlendModes.js new file mode 100644 index 0000000..5122986 --- /dev/null +++ b/src/core/utils/mapPremultipliedBlendModes.js @@ -0,0 +1,38 @@ +import { BLEND_MODES } from '../const'; + +/** + * Corrects pixi blend, takes premultiplied alpha into account + * + * @memberof PIXI + * @function mapPremultipliedBlendModes + * @private + * @param {Array} [array] - The array to output into. + * @return {Array} Mapped modes. + */ + +export default function mapPremultipliedBlendModes() +{ + const pm = []; + const npm = []; + + for (let i = 0; i < 32; i++) + { + pm[i] = i; + npm[i] = i; + } + + pm[BLEND_MODES.NORMAL_NPM] = BLEND_MODES.NORMAL; + pm[BLEND_MODES.ADD_NPM] = BLEND_MODES.ADD; + pm[BLEND_MODES.SCREEN_NPM] = BLEND_MODES.SCREEN; + + npm[BLEND_MODES.NORMAL] = BLEND_MODES.NORMAL_NPM; + npm[BLEND_MODES.ADD] = BLEND_MODES.ADD_NPM; + npm[BLEND_MODES.SCREEN] = BLEND_MODES.SCREEN_NPM; + + const array = []; + + array.push(npm); + array.push(pm); + + return array; +} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5734fc0..1225730 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -141,17 +140,13 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTint(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = renderer.bindTexture(tex); - renderer.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 9d59adb..4cd5d52 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -104,7 +104,7 @@ glData.shader.uniforms.uSampler = renderer.bindTexture(texture); - renderer.state.setBlendMode(mesh.blendMode); + renderer.state.setBlendMode(core.utils.correctBlendMode(mesh.blendMode, texture.baseTexture.premultiplyAlpha)); if (glData.shader.uniforms.uTransform) { @@ -118,8 +118,9 @@ } } glData.shader.uniforms.translationMatrix = mesh.worldTransform.toArray(true); - glData.shader.uniforms.alpha = mesh.worldAlpha; - glData.shader.uniforms.tint = mesh.tintRgb; + + glData.shader.uniforms.uColor = core.utils.premultiplyRgba(mesh._tintRgb, + mesh.worldAlpha, texture.baseTexture.premultiplyAlpha); const drawMode = mesh.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/utils/mapPremultipliedBlendModes.js b/src/core/utils/mapPremultipliedBlendModes.js new file mode 100644 index 0000000..5122986 --- /dev/null +++ b/src/core/utils/mapPremultipliedBlendModes.js @@ -0,0 +1,38 @@ +import { BLEND_MODES } from '../const'; + +/** + * Corrects pixi blend, takes premultiplied alpha into account + * + * @memberof PIXI + * @function mapPremultipliedBlendModes + * @private + * @param {Array} [array] - The array to output into. + * @return {Array} Mapped modes. + */ + +export default function mapPremultipliedBlendModes() +{ + const pm = []; + const npm = []; + + for (let i = 0; i < 32; i++) + { + pm[i] = i; + npm[i] = i; + } + + pm[BLEND_MODES.NORMAL_NPM] = BLEND_MODES.NORMAL; + pm[BLEND_MODES.ADD_NPM] = BLEND_MODES.ADD; + pm[BLEND_MODES.SCREEN_NPM] = BLEND_MODES.SCREEN; + + npm[BLEND_MODES.NORMAL] = BLEND_MODES.NORMAL_NPM; + npm[BLEND_MODES.ADD] = BLEND_MODES.ADD_NPM; + npm[BLEND_MODES.SCREEN] = BLEND_MODES.SCREEN_NPM; + + const array = []; + + array.push(npm); + array.push(pm); + + return array; +} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5734fc0..1225730 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -141,17 +140,13 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTint(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = renderer.bindTexture(tex); - renderer.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 9d59adb..4cd5d52 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -104,7 +104,7 @@ glData.shader.uniforms.uSampler = renderer.bindTexture(texture); - renderer.state.setBlendMode(mesh.blendMode); + renderer.state.setBlendMode(core.utils.correctBlendMode(mesh.blendMode, texture.baseTexture.premultiplyAlpha)); if (glData.shader.uniforms.uTransform) { @@ -118,8 +118,9 @@ } } glData.shader.uniforms.translationMatrix = mesh.worldTransform.toArray(true); - glData.shader.uniforms.alpha = mesh.worldAlpha; - glData.shader.uniforms.tint = mesh.tintRgb; + + glData.shader.uniforms.uColor = core.utils.premultiplyRgba(mesh._tintRgb, + mesh.worldAlpha, texture.baseTexture.premultiplyAlpha); const drawMode = mesh.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/mesh/webgl/mesh.frag b/src/mesh/webgl/mesh.frag index 9e0b634..6096983 100644 --- a/src/mesh/webgl/mesh.frag +++ b/src/mesh/webgl/mesh.frag @@ -1,10 +1,9 @@ varying vec2 vTextureCoord; -uniform float alpha; -uniform vec3 tint; +uniform vec4 uColor; uniform sampler2D uSampler; void main(void) { - gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha); + gl_FragColor = texture2D(uSampler, vTextureCoord) * uColor; } diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/utils/mapPremultipliedBlendModes.js b/src/core/utils/mapPremultipliedBlendModes.js new file mode 100644 index 0000000..5122986 --- /dev/null +++ b/src/core/utils/mapPremultipliedBlendModes.js @@ -0,0 +1,38 @@ +import { BLEND_MODES } from '../const'; + +/** + * Corrects pixi blend, takes premultiplied alpha into account + * + * @memberof PIXI + * @function mapPremultipliedBlendModes + * @private + * @param {Array} [array] - The array to output into. + * @return {Array} Mapped modes. + */ + +export default function mapPremultipliedBlendModes() +{ + const pm = []; + const npm = []; + + for (let i = 0; i < 32; i++) + { + pm[i] = i; + npm[i] = i; + } + + pm[BLEND_MODES.NORMAL_NPM] = BLEND_MODES.NORMAL; + pm[BLEND_MODES.ADD_NPM] = BLEND_MODES.ADD; + pm[BLEND_MODES.SCREEN_NPM] = BLEND_MODES.SCREEN; + + npm[BLEND_MODES.NORMAL] = BLEND_MODES.NORMAL_NPM; + npm[BLEND_MODES.ADD] = BLEND_MODES.ADD_NPM; + npm[BLEND_MODES.SCREEN] = BLEND_MODES.SCREEN_NPM; + + const array = []; + + array.push(npm); + array.push(pm); + + return array; +} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5734fc0..1225730 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -141,17 +140,13 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTint(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = renderer.bindTexture(tex); - renderer.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 9d59adb..4cd5d52 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -104,7 +104,7 @@ glData.shader.uniforms.uSampler = renderer.bindTexture(texture); - renderer.state.setBlendMode(mesh.blendMode); + renderer.state.setBlendMode(core.utils.correctBlendMode(mesh.blendMode, texture.baseTexture.premultiplyAlpha)); if (glData.shader.uniforms.uTransform) { @@ -118,8 +118,9 @@ } } glData.shader.uniforms.translationMatrix = mesh.worldTransform.toArray(true); - glData.shader.uniforms.alpha = mesh.worldAlpha; - glData.shader.uniforms.tint = mesh.tintRgb; + + glData.shader.uniforms.uColor = core.utils.premultiplyRgba(mesh._tintRgb, + mesh.worldAlpha, texture.baseTexture.premultiplyAlpha); const drawMode = mesh.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/mesh/webgl/mesh.frag b/src/mesh/webgl/mesh.frag index 9e0b634..6096983 100644 --- a/src/mesh/webgl/mesh.frag +++ b/src/mesh/webgl/mesh.frag @@ -1,10 +1,9 @@ varying vec2 vTextureCoord; -uniform float alpha; -uniform vec3 tint; +uniform vec4 uColor; uniform sampler2D uSampler; void main(void) { - gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha); + gl_FragColor = texture2D(uSampler, vTextureCoord) * uColor; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 6d98098..d6dc59d 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -131,8 +131,8 @@ * @member {number} * @default 0xFFFFFF */ - this._tint = null; - this._tintRGB = []; + this._tint = 0; + this.tintRgb = new Float32Array(4); this.tint = 0xFFFFFF; } @@ -180,7 +180,7 @@ set tint(value) // eslint-disable-line require-jsdoc { this._tint = value; - hex2rgb(value, this._tintRGB); + hex2rgb(value, this.tintRgb); } /** diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/utils/mapPremultipliedBlendModes.js b/src/core/utils/mapPremultipliedBlendModes.js new file mode 100644 index 0000000..5122986 --- /dev/null +++ b/src/core/utils/mapPremultipliedBlendModes.js @@ -0,0 +1,38 @@ +import { BLEND_MODES } from '../const'; + +/** + * Corrects pixi blend, takes premultiplied alpha into account + * + * @memberof PIXI + * @function mapPremultipliedBlendModes + * @private + * @param {Array} [array] - The array to output into. + * @return {Array} Mapped modes. + */ + +export default function mapPremultipliedBlendModes() +{ + const pm = []; + const npm = []; + + for (let i = 0; i < 32; i++) + { + pm[i] = i; + npm[i] = i; + } + + pm[BLEND_MODES.NORMAL_NPM] = BLEND_MODES.NORMAL; + pm[BLEND_MODES.ADD_NPM] = BLEND_MODES.ADD; + pm[BLEND_MODES.SCREEN_NPM] = BLEND_MODES.SCREEN; + + npm[BLEND_MODES.NORMAL] = BLEND_MODES.NORMAL_NPM; + npm[BLEND_MODES.ADD] = BLEND_MODES.ADD_NPM; + npm[BLEND_MODES.SCREEN] = BLEND_MODES.SCREEN_NPM; + + const array = []; + + array.push(npm); + array.push(pm); + + return array; +} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5734fc0..1225730 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -141,17 +140,13 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTint(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = renderer.bindTexture(tex); - renderer.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 9d59adb..4cd5d52 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -104,7 +104,7 @@ glData.shader.uniforms.uSampler = renderer.bindTexture(texture); - renderer.state.setBlendMode(mesh.blendMode); + renderer.state.setBlendMode(core.utils.correctBlendMode(mesh.blendMode, texture.baseTexture.premultiplyAlpha)); if (glData.shader.uniforms.uTransform) { @@ -118,8 +118,9 @@ } } glData.shader.uniforms.translationMatrix = mesh.worldTransform.toArray(true); - glData.shader.uniforms.alpha = mesh.worldAlpha; - glData.shader.uniforms.tint = mesh.tintRgb; + + glData.shader.uniforms.uColor = core.utils.premultiplyRgba(mesh._tintRgb, + mesh.worldAlpha, texture.baseTexture.premultiplyAlpha); const drawMode = mesh.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/mesh/webgl/mesh.frag b/src/mesh/webgl/mesh.frag index 9e0b634..6096983 100644 --- a/src/mesh/webgl/mesh.frag +++ b/src/mesh/webgl/mesh.frag @@ -1,10 +1,9 @@ varying vec2 vTextureCoord; -uniform float alpha; -uniform vec3 tint; +uniform vec4 uColor; uniform sampler2D uSampler; void main(void) { - gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha); + gl_FragColor = texture2D(uSampler, vTextureCoord) * uColor; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 6d98098..d6dc59d 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -131,8 +131,8 @@ * @member {number} * @default 0xFFFFFF */ - this._tint = null; - this._tintRGB = []; + this._tint = 0; + this.tintRgb = new Float32Array(4); this.tint = 0xFFFFFF; } @@ -180,7 +180,7 @@ set tint(value) // eslint-disable-line require-jsdoc { this._tint = value; - hex2rgb(value, this._tintRGB); + hex2rgb(value, this.tintRgb); } /** diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 16e0c9c..a71d397 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -142,8 +142,10 @@ buffers = container._glBuffers[renderer.CONTEXT_UID] = this.generateBuffers(container); } + const baseTexture = children[0]._texture.baseTexture; + // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); + this.renderer.setBlendMode(core.utils.correctBlendMode(container.blendMode, baseTexture.premultiplyAlpha)); const gl = renderer.gl; @@ -152,12 +154,11 @@ m.prepend(renderer._activeRenderTarget.projectionMatrix); this.shader.uniforms.projectionMatrix = m.toArray(true); - this.shader.uniforms.uAlpha = container.worldAlpha; - this.shader.uniforms.tint = container._tintRGB; + + this.shader.uniforms.uColor = core.utils.premultiplyRgba(container._tintRgb, + container.worldAlpha, baseTexture.premultiplyAlpha); // make sure the texture is bound.. - const baseTexture = children[0]._texture.baseTexture; - this.shader.uniforms.uSampler = renderer.bindTexture(baseTexture); // now lets upload and render the buffers.. diff --git a/src/core/const.js b/src/core/const.js index a26d950..b3ee851 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -104,6 +104,9 @@ SATURATION: 14, COLOR: 15, LUMINOSITY: 16, + NORMAL_NPM: 17, + ADD_NPM: 18, + SCREEN_NPM: 19, }; /** diff --git a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js index 7bf613b..6a4759d 100644 --- a/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js +++ b/src/core/renderers/canvas/utils/mapCanvasBlendModesToPixi.js @@ -53,6 +53,10 @@ array[BLEND_MODES.COLOR] = 'source-over'; array[BLEND_MODES.LUMINOSITY] = 'source-over'; } + // not-premultiplied, only for webgl + array[BLEND_MODES.NORMAL_NPM] = array[BLEND_MODES.NORMAL]; + array[BLEND_MODES.ADD_NPM] = array[BLEND_MODES.ADD]; + array[BLEND_MODES.SCREEN_NPM] = array[BLEND_MODES.SCREEN]; return array; } diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index 6c846d5..cbc0b7a 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -155,7 +155,16 @@ this.activeState[BLEND_FUNC] = value; - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); + const mode = this.blendModes[value]; + + if (mode.length === 2) + { + this.gl.blendFunc(mode[0], mode[1]); + } + else + { + this.gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } } /** diff --git a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js index 53e2111..5f4ef92 100644 --- a/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js +++ b/src/core/renderers/webgl/utils/mapWebGLBlendModesToPixi.js @@ -32,5 +32,10 @@ array[BLEND_MODES.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; array[BLEND_MODES.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + // not-premultiplied blend modes + array[BLEND_MODES.NORMAL_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + array[BLEND_MODES.ADD_NPM] = [gl.SRC_ALPHA, gl.DST_ALPHA, gl.ONE, gl.DST_ALPHA]; + array[BLEND_MODES.SCREEN_NPM] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_COLOR]; + return array; } diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 9ddc126..8fd44c1 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -5,6 +5,7 @@ import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader'; import Buffer from './BatchBuffer'; import settings from '../../settings'; +import { premultiplyBlendMode, premultiplyTint } from '../../utils'; import glCore from 'pixi-gl-core'; import bitTwiddle from 'bit-twiddle'; @@ -226,7 +227,8 @@ let currentGroup = groups[0]; let vertexData; let uvs; - let blendMode = sprites[0].blendMode; + let blendMode = premultiplyBlendMode[ + sprites[0]._texture.baseTexture.premultipliedAlpha ? 1 : 0][sprites[0].blendMode]; currentGroup.textureCount = 0; currentGroup.start = 0; @@ -251,10 +253,12 @@ nextTexture = sprite._texture.baseTexture; - if (blendMode !== sprite.blendMode) + const spriteBlendMode = premultiplyBlendMode[Number(nextTexture.premultipliedAlpha)][sprite.blendMode]; + + if (blendMode !== spriteBlendMode) { // finish a group.. - blendMode = sprite.blendMode; + blendMode = spriteBlendMode; // force the batch to break! currentTexture = null; @@ -362,10 +366,13 @@ uint32View[index + 7] = uvs[1]; uint32View[index + 12] = uvs[2]; uint32View[index + 17] = uvs[3]; - /* eslint-disable max-len */ - uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (Math.min(sprite.worldAlpha, 1) * 255 << 24); + const alpha = Math.min(sprite.worldAlpha, 1.0); + // we dont call extra function if alpha is 1.0, that's faster + const argb = alpha < 1.0 && nextTexture.premultipliedAlpha ? premultiplyTint(sprite._tintRGB, alpha) + : sprite._tintRGB + (alpha * 255 << 24); + uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = argb; float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId; /* eslint-enable max-len */ diff --git a/src/core/sprites/webgl/texture.vert b/src/core/sprites/webgl/texture.vert index 81817b1..18b89ff 100644 --- a/src/core/sprites/webgl/texture.vert +++ b/src/core/sprites/webgl/texture.vert @@ -15,5 +15,5 @@ vTextureCoord = aTextureCoord; vTextureId = aTextureId; - vColor = vec4(aColor.rgb * aColor.a, aColor.a); + vColor = aColor; } diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 22e2d98..d6bac52 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -5,6 +5,7 @@ import * as mixins from './mixin'; import * as isMobile from 'ismobilejs'; import removeItems from 'remove-array-items'; +import mapPremultipliedBlendModes from './mapPremultipliedBlendModes'; let nextUid = 0; let saidHello = false; @@ -397,3 +398,107 @@ delete BaseTextureCache[key]; } } + +/** + * @memberof PIXI.utils + * @const premultiplyBlendMode + * @type {Array} maps premultiply flag and blendMode to adjusted blendMode + */ +export const premultiplyBlendMode = mapPremultipliedBlendModes(); + +/** + * changes blendMode according to texture format + * + * @memberof PIXI.utils + * @function correctBlendMode + * @param {number} blendMode supposed blend mode + * @param {boolean} premultiplied whether source is premultiplied + * @returns {number} true blend mode for this texture + */ +export function correctBlendMode(blendMode, premultiplied) +{ + return premultiplyBlendMode[premultiplied ? 1 : 0][blendMode]; +} + +/** + * premultiplies tint + * + * @param {number} tint integet RGB + * @param {number} alpha floating point alpha (0.0-1.0) + * @returns {number} tint multiplied by alpha + */ +export function premultiplyTint(tint, alpha) +{ + if (alpha === 1.0) + { + return (alpha * 255 << 24) + tint; + } + if (alpha === 0.0) + { + return 0; + } + let R = ((tint >> 16) & 0xFF); + let G = ((tint >> 8) & 0xFF); + let B = (tint & 0xFF); + + R = ((R * alpha) + 0.5) | 0; + G = ((G * alpha) + 0.5) | 0; + B = ((B * alpha) + 0.5) | 0; + + return (alpha * 255 << 24) + (R << 16) + (G << 8) + B; +} + +/** + * combines rgb and alpha to out array + * + * @param {Float32Array|number[]} rgb input rgb + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyRgba(rgb, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + if (premultiply || premultiply === undefined) + { + out[0] = rgb[0] * alpha; + out[1] = rgb[1] * alpha; + out[2] = rgb[2] * alpha; + } + else + { + out[0] = rgb[0]; + out[1] = rgb[1]; + out[2] = rgb[2]; + } + out[3] = alpha; + + return out; +} + +/** + * converts integer tint and float alpha to vec4 form, premultiplies by default + * + * @param {number} tint input tint + * @param {number} alpha alpha param + * @param {Float32Array} [out] output + * @param {boolean} [premultiply=true] do premultiply it + * @returns {Float32Array} vec4 rgba + */ +export function premultiplyTintToRgba(tint, alpha, out, premultiply) +{ + out = out || new Float32Array(4); + out[0] = ((tint >> 16) & 0xFF); + out[1] = ((tint >> 8) & 0xFF); + out[2] = (tint & 0xFF); + if (premultiply || premultiply === undefined) + { + out[0] *= alpha; + out[1] *= alpha; + out[2] *= alpha; + } + out[3] = alpha; + + return out; +} diff --git a/src/core/utils/mapPremultipliedBlendModes.js b/src/core/utils/mapPremultipliedBlendModes.js new file mode 100644 index 0000000..5122986 --- /dev/null +++ b/src/core/utils/mapPremultipliedBlendModes.js @@ -0,0 +1,38 @@ +import { BLEND_MODES } from '../const'; + +/** + * Corrects pixi blend, takes premultiplied alpha into account + * + * @memberof PIXI + * @function mapPremultipliedBlendModes + * @private + * @param {Array} [array] - The array to output into. + * @return {Array} Mapped modes. + */ + +export default function mapPremultipliedBlendModes() +{ + const pm = []; + const npm = []; + + for (let i = 0; i < 32; i++) + { + pm[i] = i; + npm[i] = i; + } + + pm[BLEND_MODES.NORMAL_NPM] = BLEND_MODES.NORMAL; + pm[BLEND_MODES.ADD_NPM] = BLEND_MODES.ADD; + pm[BLEND_MODES.SCREEN_NPM] = BLEND_MODES.SCREEN; + + npm[BLEND_MODES.NORMAL] = BLEND_MODES.NORMAL_NPM; + npm[BLEND_MODES.ADD] = BLEND_MODES.ADD_NPM; + npm[BLEND_MODES.SCREEN] = BLEND_MODES.SCREEN_NPM; + + const array = []; + + array.push(npm); + array.push(pm); + + return array; +} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js index 5734fc0..1225730 100644 --- a/src/extras/webgl/TilingSpriteRenderer.js +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -4,7 +4,6 @@ import { join } from 'path'; const tempMat = new core.Matrix(); -const tempArray = new Float32Array(4); /** * WebGL renderer plugin for tiling sprites @@ -141,17 +140,13 @@ } shader.uniforms.uTransform = tempMat.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(ts.tint, color); - color[3] = ts.worldAlpha; - shader.uniforms.uColor = color; + shader.uniforms.uColor = core.utils.premultiplyTint(ts.tint, ts.worldAlpha, + shader.uniforms.uColor, baseTex.premultiplyAlpha); shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); shader.uniforms.uSampler = renderer.bindTexture(tex); - renderer.setBlendMode(ts.blendMode); + renderer.setBlendMode(core.utils.correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha)); quad.vao.draw(this.renderer.gl.TRIANGLES, 6, 0); } diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 9d59adb..4cd5d52 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -104,7 +104,7 @@ glData.shader.uniforms.uSampler = renderer.bindTexture(texture); - renderer.state.setBlendMode(mesh.blendMode); + renderer.state.setBlendMode(core.utils.correctBlendMode(mesh.blendMode, texture.baseTexture.premultiplyAlpha)); if (glData.shader.uniforms.uTransform) { @@ -118,8 +118,9 @@ } } glData.shader.uniforms.translationMatrix = mesh.worldTransform.toArray(true); - glData.shader.uniforms.alpha = mesh.worldAlpha; - glData.shader.uniforms.tint = mesh.tintRgb; + + glData.shader.uniforms.uColor = core.utils.premultiplyRgba(mesh._tintRgb, + mesh.worldAlpha, texture.baseTexture.premultiplyAlpha); const drawMode = mesh.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/mesh/webgl/mesh.frag b/src/mesh/webgl/mesh.frag index 9e0b634..6096983 100644 --- a/src/mesh/webgl/mesh.frag +++ b/src/mesh/webgl/mesh.frag @@ -1,10 +1,9 @@ varying vec2 vTextureCoord; -uniform float alpha; -uniform vec3 tint; +uniform vec4 uColor; uniform sampler2D uSampler; void main(void) { - gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha); + gl_FragColor = texture2D(uSampler, vTextureCoord) * uColor; } diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 6d98098..d6dc59d 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -131,8 +131,8 @@ * @member {number} * @default 0xFFFFFF */ - this._tint = null; - this._tintRGB = []; + this._tint = 0; + this.tintRgb = new Float32Array(4); this.tint = 0xFFFFFF; } @@ -180,7 +180,7 @@ set tint(value) // eslint-disable-line require-jsdoc { this._tint = value; - hex2rgb(value, this._tintRGB); + hex2rgb(value, this.tintRgb); } /** diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 16e0c9c..a71d397 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -142,8 +142,10 @@ buffers = container._glBuffers[renderer.CONTEXT_UID] = this.generateBuffers(container); } + const baseTexture = children[0]._texture.baseTexture; + // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); + this.renderer.setBlendMode(core.utils.correctBlendMode(container.blendMode, baseTexture.premultiplyAlpha)); const gl = renderer.gl; @@ -152,12 +154,11 @@ m.prepend(renderer._activeRenderTarget.projectionMatrix); this.shader.uniforms.projectionMatrix = m.toArray(true); - this.shader.uniforms.uAlpha = container.worldAlpha; - this.shader.uniforms.tint = container._tintRGB; + + this.shader.uniforms.uColor = core.utils.premultiplyRgba(container._tintRgb, + container.worldAlpha, baseTexture.premultiplyAlpha); // make sure the texture is bound.. - const baseTexture = children[0]._texture.baseTexture; - this.shader.uniforms.uSampler = renderer.bindTexture(baseTexture); // now lets upload and render the buffers.. diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index ebd80d9..4361623 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -48,11 +48,10 @@ 'varying float vColor;', 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', - 'uniform vec3 tint;', + 'uniform vec4 uColor;', 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * vec4(tint * uAlpha, uAlpha);', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uColor;', ' if (color.a == 0.0) discard;', ' gl_FragColor = color;', '}',